如果您想通过“页面数字”进行分页,那么您基本上只能使用在对键排序后应用的.limit()
和.skip()
方法,您可能已经阅读了一些内容,并发现这种方法“效率不高”,主要是由于“跳过”“n”个结果以到达特定页面的成本很高。
但当您需要时,这个原则是有道理的:
db.collection.find().sort({ "s": -1, "_id": 1 }).skip(<page-1>).limit(<pageSize>)
如果您只需要在分页中向“前”移动,那么有一种更快的替代方法,也适用于“已排序”的结果。
关键是保持对“s”的“最后可见”值的引用,并通常保留_id
值列表,直到该值的“s”发生更改。因此,演示一下更多已经排序过的文档:
{ "_id": 1, "s": 3 },
{ "_id": 2, "s": 3 },
{ "_id": 3, "s": 3 },
{ "_id": 4, "s": 2 },
{ "_id": 5, "s": 1 },
{ "_id": 6, "s": 1 },
为了获取“两个”结果的“第一页”,您的第一个查询很简单:
db.collection.find().sort({ "s": -1, "_id": 1}).limit(2)
但是将其延伸到处理文件方面:
var lastVal = null,
lastSeen = [];
db.collection.find().sort({ "s": -1, "_id": 1}).limit(2).forEach(function(doc) {
if ( doc.s != lastVal ) {
lastVal = doc.s;
lastSeen = [];
}
lastSeen.push(doc._id);
})
所以在第一次迭代中,lastVal
的值将是3
,而lastSeen
将包含数组[1,2]
中文档_id
的值。
这些东西你会存储在类似于用户会话数据的东西中,等待下一页请求。
然后在您请求下一页设置时,您应该发出以下命令:
var lastVal = 3,
lastSeen = [1,2];
db.collection.find({
"_id": { "$nin": lastSeen },
"s": { "$lte": lastVal }
}).sort({ "s": -1, "_id": 1}).limit(2).forEach(function(doc) {
if ( doc.s != lastVal ) {
lastVal = doc.s;
lastSeen = [];
}
lastSeen.push(doc._id);
})
这要求选择“s”的起始值必须是小于等于(由于排序的方向)记录的lastVal
的值,并且“_id”字段不能包含在lastSeen
中记录的值。
结果的下一页是:
{ "_id": 3, "s": 3 },
{ "_id": 4, "s": 2 },
但是现在,如果您按照逻辑,lastVal
当然是 2
,而 lastSeen
现在仅具有单个数组元素 [4]
。由于接下来的查询只需要从 2
开始作为小于或等于值进行跟随,因此无需保留其他先前查看过的 "_id" 值,因为它们不会在该选择范围内。
然后这个过程就继续了:
var lastVal = 2,
lastSeen = [2];
db.collection.find({
"_id": { "$nin": lastSeen },
"s": { "$lte": lastVal }
}).sort({ "s": -1, "_id": 1}).limit(2).forEach(function(doc) {
if ( doc.s != lastVal ) {
lastVal = doc.s;
lastSeen = [];
}
lastSeen.push(doc._id);
})
所以,通过遵循这种逻辑模式,您可以“存储”从“先前的页面”中找到的信息,并通过结果非常高效地向“前进”。
但是,如果您需要跳转到“第20页”或类似的操作,则只能使用.limit()
和.skip()
。 这样做速度会慢一些,但这取决于您能接受什么。
s < lastVal or (s == lastVal and _id > lastId)
。这样你就可以省略IN
操作,因为它们往往比简单的比较慢。 - Genarito