我知道使用skip来实现分页是一种不好的做法,因为当数据变大时,skip开始消耗大量内存.克服这个问题的一种方法是使用_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分的最好方法

推荐答案

您所谈论的概念可以称为"前向分页".这样做的一个很好的原因是,与使用.skip().limit()修饰符不同,这不能用于"返回"到上一页,或者实际上是"跳过"到特定页.至少不需要花费大量精力来存储"看到的"或"发现的"页面,因此,如果这种类型的"页面链接"页面是您想要的,那么您最好还是坚持使用.skip().limit()方法,尽管存在性能缺陷.

如果只"向前迈进"对你来说是一个可行的 Select ,那么以下是基本概念:

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 }

在下一次迭代中,您希望小于或等于上次看到的"排名"分数,但也不包括那些已经看到的文档.您可以使用$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 }

你实际持有的"seenIds"数量取决于你的结果的"粒度",即该值可能发生变化的地方.在这种情况下,您可以判断当前的"排名"分数是否不等于lastSeen分,并放弃当前的seenIds内容,这样它就不会增长太多.

这就是"转发分页"的基本概念,供您练习和学习.

Mongodb相关问答推荐

映射数组值并查找每个匹配的集合

我无法将文档发送到我的MongoDB集合,因为它告诉我使文档无效

Next js、MongoDB和Prisma.包括不为相关字段工作

匹配/筛选/投影对象中数组中数组中的嵌套字段

MongoDB:如何从数组中的所有对象中删除属性?

mongo如何通过聚合加载嵌套文档

Mongodb聚合查找连接两个对象字段的集合数组匹配对象索引字段的总和

Mongoose 更新不同类型的记录

在mongodb中,如何使用聚合来获取字段之间的对应关系

根据 Month 删除 mongodb 中的旧记录

SELECT 字段 AS `anothername` 的 mongodb 等效项

我怎样才能排序空值在 mongodb 中是最后排序的?

mongodump 是否锁定数据库?

如何在mongo中插入带有日期的文档?

Node + Mongoose:获取最后插入的 ID?

单个模式数组中的多个模式引用 - mongoose

我如何使用 Twitter 的流 api 中的推文并将它们存储在 mongodb 中

更新时提示Field name duplication not allowed with modifiers

MongoDB备份计划

MongoDB聚合框架的索引优化