MongoDB远程分页


69

据说在具有许多记录的MongoDB集合中使用skip()进行分页很慢,因此不建议使用。

可以使用远程分页(基于> _id比较)

db.items.find({_id: {$gt: ObjectId('4f4a3ba2751e88780b000000')}});

显示上一个很有用。&next按钮-但是要显示实际的页码1 ... 5 6 7 ... 124时,实现起来并不容易-您需要预先计算每个页面从哪个“ _id”开始。

所以我有两个问题:

1)我什么时候应该开始担心呢?当“太多记录”中skip()的运行速度明显变慢时?一千?1000000?

2)使用远程分页时,显示带有实际页码的链接的最佳方法是什么?

Answers:


99

好问题!

“多少太多?” -当然,这取决于您的数据大小和性能要求。当我跳过500-1000条以上的记录时,我个人感到不舒服。

实际答案取决于您的要求。这是现代网站的功能(或至少其中一些功能)。

首先,导航栏如下所示:

1 2 3 ... 457

他们从总记录数和页面大小中获得最终页面号。让我们跳到第3页。这将涉及从第一条记录中跳过一些内容。结果到达时,您将在第3页上知道第一条记录的ID。

1 2 3 4 5 ... 457

让我们跳过一些,转到第5页。

1 ... 3 4 5 6 7 ... 457

你明白了。在每个点上,您都可以看到第一页,最后一页和当前页面,以及从当前页面向前和向后两个页面。

查询

var current_id; // id of first record on current page.

// go to page current+N
db.collection.find({_id: {$gte: current_id}}).
              skip(N * page_size).
              limit(page_size).
              sort({_id: 1});

// go to page current-N
// note that due to the nature of skipping back,
// this query will get you records in reverse order 
// (last records on the page being first in the resultset)
// You should reverse them in the app.
db.collection.find({_id: {$lt: current_id}}).
              skip((N-1)*page_size).
              limit(page_size).
              sort({_id: -1});

谢谢,这正是我所需要的。很棒的组合方法-范围“ _id” + skip(),非常易于使用,比我今天搜索主题时阅读的所有方法都好得多。
罗马

1
很好的答案,但是通过这种方法,您必须知道当前的页码。知道它的唯一方法-是在请求中发送它
真空

1
如果索引需要反转,这种方法会起作用吗?sort({_ id:-1})
真空

1
还有一个问题:如何有效获取最后一页?
真空

1
澄清-如果存在重复值,这将不起作用。万一其他人偶然发现了这个问题,我通过遵循mixmax.com/blog/api-paging-built-the-right-way并使其适应我自己的需求来克服了这一限制。
阿维乌斯(Avios)'18 -10-22

6

很难给出一个普遍的答案,因为它很大程度上取决于您要使用哪些查询来构造要显示的结果集。如果仅使用索引可以找到结果并按索引顺序显示,则即使有大量跳过,db.dataset.find()。limit()。skip()也可以表现良好。这可能是最简单的编码方法。但是即使在这种情况下,例如,如果您可以缓存页码并将其与索引值绑定,则可以使想要查看第71页的第二人和第三人更快。

在一个非常动态的数据集中,当其他人在翻阅数据时将添加和删除文档时,这种缓存将很快过时,并且limit和skip方法可能是唯一可以提供良好结果的可靠方法。


1

我最近在尝试使用非唯一字段(例如“ FirstName”)进行分页请求时遇到相同的问题。此查询的思想是能够在不使用unique的字段上实现分页,而无需使用skip()

这里的主要问题是能够查询不是唯一的“ FirstName”字段,因为会发生以下情况:

  1. $ gt:{“ FirstName”:“ Carlos”}->这将跳过名字为“ Carlos”的所有记录
  2. $ gte:{“ FirstName”:“ Carlos”}->将始终返回同一组数据

因此,我想出的解决方案是通过将目标搜索字段与辅助字段组合在一起,使查询的$ match部分变得唯一,从而使其成为唯一搜索。

升序:

db.customers.aggregate([
    {$match: { $or: [ {$and: [{'FirstName': 'Carlos'}, {'_id': {$gt: ObjectId("some-object-id")}}]}, {'FirstName': {$gt: 'Carlos'}}]}},
    {$sort: {'FirstName': 1, '_id': 1}},
    {$limit: 10}
    ])

降序排列:

db.customers.aggregate([
    {$match: { $or: [ {$and: [{'FirstName': 'Carlos'}, {'_id': {$gt: ObjectId("some-object-id")}}]}, {'FirstName': {$lt: 'Carlos'}}]}},
    {$sort: {'FirstName': -1, '_id': 1}},
    {$limit: 10}
    ])

此查询的$ match部分基本上表现为if语句:如果firstName为“ Carlos”,则它也必须大于此id,如果firstName不等于“ Carlos”,则它必须大于“ Carlos”

唯一的问题是您无法导航到特定的页码(可以通过一些代码操作来完成),但是它解决了我对非唯一字段进行分页的问题,而不必使用跳过这会占用大量内存和处理时间到达要查询的任何数据集的结尾时的强大功能。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.