【问题标题】:Slow pagination over tons of records in mongodb对 mongodb 中的大量记录进行缓慢分页
【发布时间】:2011-11-05 21:21:10
【问题描述】:

我在 Mongo 的一个集合中有超过 30 万条记录。

当我运行这个非常简单的查询时:

db.myCollection.find().limit(5);

只需几毫秒。

但是当我在查询中使用跳过时:

db.myCollection.find().skip(200000).limit(5)

它不会返回任何东西......它运行了几分钟并且什么也没返回。

如何让它变得更好?

【问题讨论】:

    标签: mongodb


    【解决方案1】:

    来自 MongoDB documentation:

    寻呼费用

    不幸的是,跳过可能(非常)代价高昂,并且需要服务器从集合或索引的开头步行到偏移/跳过位置,然后才能开始返回数据页(限制)。随着页码的增加,skip 会变得更慢,CPU 密集度更高,并且可能会受到 IO 限制,并且集合更大。

    基于范围的分页可以更好地使用索引,但不允许您轻松跳转到特定页面。

    您必须问自己一个问题:您多久需要第 40000 页?另见this文章;

    【讨论】:

    • 它是一个INDEX,它应该是瞬时的,最糟糕的是如果你有100万个文档,跳过1000万个它仍然需要一分钟,这很难理解,不是吗mongo 随时知道一个集合有多少个文档?但我的主要观点是,为什么获取第 n 个文件很慢?这不是我们首先使用数据库的原因吗?
    • @MartijnScheffer 有史以来最好的评论
    • 当你做一个裸查找(可爱的哈)时,它实际上根本不使用索引(因为你没有任何查询/过滤器)......所以它正在做一个完整的 COLSCAN一路检查每个项目(文档)。您可以尝试使用此技巧至少使用默认 ID 索引... db.myCollection.find({'_id': {'$gt': ''}}).skip(20000).limit(5) 类似这至少会删除大型文档扫描,坚持索引扫描。使用跳过/限制时,您必须使用完全包含在索引中的过滤器才能坚持索引扫描,否则必须去文档找出问题。
    • 不使用 _id 进行查询,您也可以只按 _id 排序来触发索引使用。 db.myCollection.find().skip(20000).sort({"_id":1}).limit(5).explain("executionStats")
    • @xEverybodyx 我有这种经验,即使使用索引,.skip 也会严重减速。
    【解决方案2】:

    解决此问题的一种方法是,如果您有大量文档并且以 已排序 顺序显示它们(我不确定 skip 是否有用)会是使用您要排序的键来选择下一页结果。

    所以如果你从

    db.myCollection.find().limit(100).sort({created_date:true});
    

    然后将光标返回的last文档的创建日期提取到变量max_created_date_from_last_result中,您可以获得效率更高的下一页(假设您在@上有一个索引987654326@) 查询

    db.myCollection.find({created_date : { $gt : max_created_date_from_last_result } }).limit(100).sort({created_date:true}); 
    

    【讨论】:

    • 这看起来很不错。为什么我没有看到更多人提出这个建议?
    • 嗯,它的局限性在于您一次只能前进或后退一页,而不是跳到特定页面,但对于这个有限的用例,我认为它运作良好。
    • 我发现这个答案使用了与上面类似的机制:stackoverflow.com/a/9704204/1015147 这可能会有所帮助。
    • 此方法应谨慎使用,因为如果created_datemax_created_date_from_last_result 相同,则会导致结果被忽略。请参阅sammaye.wordpress.com/2012/05/25/…,它提供了在_id 和时间戳上使用复合索引的解决方案。
    • 这是一种合理的方法,但并不完美,因为您不能跳转页面。我看到的一个限制是,如果记录按名称排序怎么办,例如:product.name。我很惊讶在 MangoDB 中没有真正支持 skip,这对我来说是一个交易破坏者。
    【解决方案3】:

    我发现将这两个概念结合在一起(skip+limit 和 find+limit)非常有效。当您有很多文档(尤其是较大的文档)时,skip+limit 的问题是性能不佳。 find+limit 的问题是你不能跳转到任意页面。我希望能够在不按顺序进行分页的情况下进行分页。

    我采取的步骤是:

    1. 根据您希望对文档进行排序的方式创建索引,或者只使用默认的 _id 索引(我使用的是该索引)
    2. 知道起始值、页面大小和要跳转到的页面
    3. 项目 + 跳过 + 限制你应该开始的值
    4. 查找+限制页面结果

    如果我想获取 16 条记录的第 5432 页(在 javascript 中),它看起来大致是这样的:

    let page = 5432;
    let page_size = 16;
    let skip_size = page * page_size;
    
    let retval = await db.collection(...).find().sort({ "_id": 1 }).project({ "_id": 1 }).skip(skip_size).limit(1).toArray();
    let start_id = retval[0].id;
    
    retval = await db.collection(...).find({ "_id": { "$gte": new mongo.ObjectID(start_id) } }).sort({ "_id": 1 }).project(...).limit(page_size).toArray();
    

    这是可行的,因为即使您要跳过数百万条记录(这就是我正在做的),对投影索引的跳过也非常快。如果你运行explain("executionStats"),它仍然有很多totalDocsExamined,但由于索引上的投影,它非常快(基本上,数据块永远不会被检查)。然后,有了页面开头的值,您可以非常快速地获取下一页。

    【讨论】:

    • 我尝试了几百万条记录,但预测增加了查询时间
    • 好吧,@Mr. T,投影在 MongoDB 中不使用索引。
    【解决方案4】:

    我连接了两个答案。

    问题是当您使用跳过和限制时,没有排序,它只是按表的顺序分页,与您将数据写入表的顺序相同,因此引擎需要创建第一个临时索引。使用现成的 _id 索引更好:) 您需要使用按 _id 排序。比像大桌子一样快。

    db.myCollection.find().skip(4000000).limit(1).sort({ "_id": 1 });
    

    在 PHP 中是

    $manager = new \MongoDB\Driver\Manager("mongodb://localhost:27017", []);
    $options = [
                'sort' => array('_id' => 1),
                'limit' => $limit, 
                'skip' => $skip,
    
            ];
    $where = [];
    $query = new \MongoDB\Driver\Query($where, $options );
    $get = $manager->executeQuery("namedb.namecollection", $query);
    

    【讨论】:

      【解决方案5】:

      我的收藏有大约 130 万个文档(不是那么大),已正确编入索引,但该问题仍然对性能造成很大影响。

      看了其他答案,前面的解决方案就很清楚了;分页集合必须按类似于 SQL 的自动增量值的计数整数排序,而不是基于时间的值。

      问题在于skip;没有其他方法可以解决它;如果您使用skip,当您的收藏增加时,您一定会遇到这个问题。
      使用带有索引的计数整数允许您使用索引而不是跳过来跳转。这不适用于基于时间的值,因为您无法根据时间计算跳转的位置,因此在后一种情况下,跳过是唯一的选择。

      另一方面,
      通过为每个文档分配一个计数,写入性能会受到影响;因为所有文档都必须按顺序插入。这对我的用例来说很好,但我知道解决方案并不适合所有人。
      最受欢迎的答案似乎不适用于我的情况,但这个答案确实适用。 (我需要能够通过任意页码向前搜索,而不是一次一个。)

      另外,如果您正在处理delete,这也很困难,但仍然可能,因为 MongoDB 支持 $inc 并带有负值进行批量更新。幸运的是,我不必处理我正在维护的应用程序中的删除问题。

      把它写下来,作为对未来自己的说明。用我正在处理的当前应用程序解决这个问题可能太麻烦了,但是下次如果遇到类似情况,我会构建一个更好的。

      【讨论】:

        猜你喜欢
        • 2014-04-05
        • 2019-11-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-05
        • 1970-01-01
        相关资源
        最近更新 更多