【发布时间】:2011-11-05 21:21:10
【问题描述】:
我在 Mongo 的一个集合中有超过 30 万条记录。
当我运行这个非常简单的查询时:
db.myCollection.find().limit(5);
只需几毫秒。
但是当我在查询中使用跳过时:
db.myCollection.find().skip(200000).limit(5)
它不会返回任何东西......它运行了几分钟并且什么也没返回。
如何让它变得更好?
【问题讨论】:
标签: mongodb
我在 Mongo 的一个集合中有超过 30 万条记录。
当我运行这个非常简单的查询时:
db.myCollection.find().limit(5);
只需几毫秒。
但是当我在查询中使用跳过时:
db.myCollection.find().skip(200000).limit(5)
它不会返回任何东西......它运行了几分钟并且什么也没返回。
如何让它变得更好?
【问题讨论】:
标签: mongodb
来自 MongoDB documentation:
寻呼费用
不幸的是,跳过可能(非常)代价高昂,并且需要服务器从集合或索引的开头步行到偏移/跳过位置,然后才能开始返回数据页(限制)。随着页码的增加,skip 会变得更慢,CPU 密集度更高,并且可能会受到 IO 限制,并且集合更大。
基于范围的分页可以更好地使用索引,但不允许您轻松跳转到特定页面。
您必须问自己一个问题:您多久需要第 40000 页?另见this文章;
【讨论】:
解决此问题的一种方法是,如果您有大量文档并且以 已排序 顺序显示它们(我不确定 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});
【讨论】:
created_date 与max_created_date_from_last_result 相同,则会导致结果被忽略。请参阅sammaye.wordpress.com/2012/05/25/…,它提供了在_id 和时间戳上使用复合索引的解决方案。
product.name。我很惊讶在 MangoDB 中没有真正支持 skip,这对我来说是一个交易破坏者。
我发现将这两个概念结合在一起(skip+limit 和 find+limit)非常有效。当您有很多文档(尤其是较大的文档)时,skip+limit 的问题是性能不佳。 find+limit 的问题是你不能跳转到任意页面。我希望能够在不按顺序进行分页的情况下进行分页。
我采取的步骤是:
如果我想获取 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,但由于索引上的投影,它非常快(基本上,数据块永远不会被检查)。然后,有了页面开头的值,您可以非常快速地获取下一页。
【讨论】:
我连接了两个答案。
问题是当您使用跳过和限制时,没有排序,它只是按表的顺序分页,与您将数据写入表的顺序相同,因此引擎需要创建第一个临时索引。使用现成的 _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);
【讨论】:
我的收藏有大约 130 万个文档(不是那么大),已正确编入索引,但该问题仍然对性能造成很大影响。
看了其他答案,前面的解决方案就很清楚了;分页集合必须按类似于 SQL 的自动增量值的计数整数排序,而不是基于时间的值。
问题在于skip;没有其他方法可以解决它;如果您使用skip,当您的收藏增加时,您一定会遇到这个问题。
使用带有索引的计数整数允许您使用索引而不是跳过来跳转。这不适用于基于时间的值,因为您无法根据时间计算跳转的位置,因此在后一种情况下,跳过是唯一的选择。
另一方面,
通过为每个文档分配一个计数,写入性能会受到影响;因为所有文档都必须按顺序插入。这对我的用例来说很好,但我知道解决方案并不适合所有人。
最受欢迎的答案似乎不适用于我的情况,但这个答案确实适用。 (我需要能够通过任意页码向前搜索,而不是一次一个。)
另外,如果您正在处理delete,这也很困难,但仍然可能,因为 MongoDB 支持 $inc 并带有负值进行批量更新。幸运的是,我不必处理我正在维护的应用程序中的删除问题。
把它写下来,作为对未来自己的说明。用我正在处理的当前应用程序解决这个问题可能太麻烦了,但是下次如果遇到类似情况,我会构建一个更好的。
【讨论】: