在 mongodb 中实现分页

IT技术 javascript mongodb pagination mongodb-query
2021-01-22 00:49:59

我知道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

2个回答

您所说的概念可以称为“前向分页”。一个很好的理由是与使用.skip().limit()修饰符不同它不能用于“返回”上一页或“跳”到特定页面。至少不需要花费大量精力来存储“看过的”或“发现的”页面,所以如果这种类型的“页面链接”分页是你想要的,那么你最好坚持使用.skip()and.limit()方法,尽管性能缺点。

如果仅“前进”对您来说是一个可行的选择,那么这里是基本概念:

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 在线;)
2021-03-29 00:49:59
@Disposer 移动应用程序和机器人。机器的兴起。
2021-03-30 00:49:59

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 个元素,这会降低性能
2021-03-17 00:49:59