MongoDB 范围分页
Posted
技术标签:
【中文标题】MongoDB 范围分页【英文标题】:MongoDB ranged pagination 【发布时间】:2012-03-30 23:48:55 【问题描述】:据说在有很多记录的MongoDB集合中使用skip()进行分页很慢,不推荐。
可以使用范围分页(基于>_id比较)
db.items.find(_id: $gt: ObjectId('4f4a3ba2751e88780b000000'));
它有利于显示上一页。 & 下一个按钮 - 但当您想要显示实际页码 1 ... 5 6 7 ... 124 时,实现起来并不容易 - 您需要预先计算每个页面从哪个“_id”开始。
所以我有两个问题:
1) 我应该什么时候开始担心这个问题?当有“太多记录”时,skip() 速度明显减慢? 1000? 1 000 000?
2) 使用范围分页时,用实际页码显示链接的最佳方法是什么?
【问题讨论】:
【参考方案1】:好问题!
“多少才算太多?” - 当然,这取决于您的数据大小和性能要求。当我跳过超过 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() 的范围,非常易于使用,比我今天在搜索主题时阅读的所有方法都要好。 很好的答案,但在这种方法中,您必须知道当前页码。知道它的唯一方法 - 是在请求中发送它 如果索引需要反转,这会起作用吗? 排序(_id: -1) 还有一个问题:如何有效获取最后一页? 澄清 - 如果有重复的值,这将不起作用。万一其他人偶然发现了这一点,我设法通过关注mixmax.com/blog/api-paging-built-the-right-way 并根据自己的需要进行调整来克服这个限制。【参考方案2】:很难给出一个笼统的答案,因为它很大程度上取决于您使用什么查询(或多个查询)来构建正在显示的结果集。如果仅使用索引可以找到结果并按索引顺序显示,那么 db.dataset.find().limit().skip() 即使有大量跳过也可以很好地执行。这可能是最简单的编码方法。但即使在这种情况下,如果您可以缓存页码并将它们与索引值相关联,例如,您可以为想要查看第 71 页的第二人和第三人提供更快的速度。
在一个非常动态的数据集中,当其他人正在对数据进行分页时,文档将被添加和删除,这样的缓存将很快过时,而 limit 和 skip 方法可能是唯一一种足够可靠以提供良好结果的方法.
【讨论】:
【参考方案3】:我最近在尝试使用非唯一字段(例如“FirstName”)对请求进行分页时遇到了同样的问题。这个查询的想法是能够在不使用skip()的情况下对非唯一字段实现分页
这里的主要问题是能够查询不是唯一的“FirstName”字段,因为会发生以下情况:
-
$gt: "FirstName": "Carlos" -> 这将跳过名字为“Carlos”的所有记录
$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”
唯一的问题是您无法导航到特定的页码(可能可以通过一些代码操作来完成),但除此之外它解决了我的非唯一字段分页问题,而不必使用跳过会吃很多东西到达您要查询的任何数据集的末尾时的内存和处理能力。
【讨论】:
以上是关于MongoDB 范围分页的主要内容,如果未能解决你的问题,请参考以下文章