在mongodb中实现分页
Posted
技术标签:
【中文标题】在mongodb中实现分页【英文标题】:Implementing pagination in mongodb 【发布时间】:2015-03-22 04:52:34 【问题描述】:我知道使用skip
来实现分页是一种不好的做法,因为当您的数据变大时skip
开始消耗大量内存。解决此问题的一种方法是使用自然顺序 by _id
字段:
//Page 1
db.users.find().limit(pageSize);
//Find the id of the last document in this page
last_id = ...
//Page 2
users = db.users.find('_id'> last_id). limit(10);
问题是 - 我是 mongo 的新手,不知道获得这个的最佳方法是什么last_id
【问题讨论】:
docs.mongodb.org/manual/reference/operator/query/gt 谢谢!但我看不出我应该如何获得last_id。我应该使用什么代码来代替那些点:last_id = ...
?
【参考方案1】:
您所说的概念可以称为“转发分页”。一个很好的理由与使用 .skip()
和 .limit()
修饰符不同,这不能用于“返回”到前一页或确实“跳过”到特定页面。至少不需要花费大量精力来存储“看到”或“发现”页面,所以如果你想要那种类型的“页面链接”分页,那么你最好坚持使用 .skip()
和 @987654325 @ 方法,尽管存在性能缺陷。
如果只“向前”对您来说是一个可行的选择,那么基本概念如下:
db.junk.find().limit(3)
"_id" : ObjectId("54c03f0c2f63310180151877"), "a" : 1, "b" : 1
"_id" : ObjectId("54c03f0c2f63310180151878"), "a" : 4, "b" : 4
"_id" : ObjectId("54c03f0c2f63310180151879"), "a" : 10, "b" : 10
当然,这是您的第一页,限制为 3 项。现在考虑一下迭代光标的代码:
var lastSeen = null;
var cursor = db.junk.find().limit(3);
while (cursor.hasNext())
var doc = cursor.next();
printjson(doc);
if (!cursor.hasNext())
lastSeen = doc._id;
这样迭代光标并执行某些操作,当确实到达光标中的最后一项时,您将lastSeen
值存储到当前_id
:
ObjectId("54c03f0c2f63310180151879")
在您随后的迭代中,您只需将您保留(在会话中或其他任何地方)的 _id
值提供给查询:
var cursor = db.junk.find( "_id": "$gt": lastSeen ).limit(3);
while (cursor.hasNext())
var doc = cursor.next();
printjson(doc);
if (!cursor.hasNext())
lastSeen = doc._id;
"_id" : ObjectId("54c03f0c2f6331018015187a"), "a" : 1, "b" : 1
"_id" : ObjectId("54c03f0c2f6331018015187b"), "a" : 6, "b" : 6
"_id" : ObjectId("54c03f0c2f6331018015187c"), "a" : 7, "b" : 7
并且这个过程一遍又一遍地重复,直到无法获得更多结果。
这是_id
等自然秩序的基本过程。对于其他事情,它会变得更复杂一些。考虑以下几点:
"_id": 4, "rank": 3
"_id": 8, "rank": 3
"_id": 1, "rank": 3
"_id": 3, "rank": 2
要将其分成按排名排序的两个页面,那么您基本上需要知道的是您“已经看到”的内容并排除这些结果。所以看第一页:
var lastSeen = null;
var seenIds = [];
var cursor = db.junk.find().sort( "rank": -1 ).limit(2);
while (cursor.hasNext())
var doc = cursor.next();
printjson(doc);
if ( lastSeen != null && doc.rank != lastSeen )
seenIds = [];
seenIds.push(doc._id);
if (!cursor.hasNext() || lastSeen == null)
lastSeen = doc.rank;
"_id": 4, "rank": 3
"_id": 8, "rank": 3
在下一次迭代中,您希望小于或等于 lastSeen“排名”分数,但也排除那些已经看过的文档。您可以使用 $nin
运算符来执行此操作:
var cursor = db.junk.find(
"_id": "$nin": seenIds , "rank": "$lte": lastSeen
).sort( "rank": -1 ).limit(2);
while (cursor.hasNext())
var doc = cursor.next();
printjson(doc);
if ( lastSeen != null && doc.rank != lastSeen )
seenIds = [];
seenIds.push(doc._id);
if (!cursor.hasNext() || lastSeen == null)
lastSeen = doc.rank;
"_id": 1, "rank": 3
"_id": 3, "rank": 2
您实际持有多少“seenId”取决于您的结果在该值可能发生变化的地方有多“精细”。在这种情况下,您可以检查当前的“排名”分数是否不等于 lastSeen
值并丢弃当前的 seenIds
内容,使其不会增长太多。
这就是“转发分页”的基本概念,供大家练习和学习。
【讨论】:
@Neil-Lunn,这样一个完整而精彩的解释。我发现了一些关于你的事情,你似乎从不睡觉,我监视你(开玩笑),看到你总是 24/7 在线;) @Disposer 移动应用程序和机器人。机器的崛起。【参考方案2】:在MongoDB中实现分页的最简单方法
// Pagination
const page = parseInt(req.query.page, 10) || 1;
const limit = parseInt(req.query.limit, 10) || 25;
const startIndex = (page - 1) * limit;
const endIndex = page * limit;
query = query.skip(startIndex).limit(limit);
【讨论】:
这种方法有它的缺点。它必须跳过前 n 个元素,这意味着它必须读取前 n 个元素,这会降低性能以上是关于在mongodb中实现分页的主要内容,如果未能解决你的问题,请参考以下文章