推文“实际”时间戳(即发布推文的时间和您希望对其排序的标准)与推文“插入”时间戳(即添加到本地集合的时间)不会不同。当然,这取决于您的应用程序,但很可能会出现推文插入可能被批处理或以其他方式最终以“错误”顺序插入的情况。因此,除非您在 Twitter 工作(并且可以访问以正确顺序插入的集合),否则您将无法仅依赖 $natural 或 ObjectID 进行排序逻辑。
Mongo 文档建议 skip and limit for paging:
db.tweets.find({created: {$lt: maxID}).
sort({created: -1, username: 1}).
skip(50).limit(50); //second page
但是,使用 skip 时存在性能问题:
cursor.skip() 方法通常代价高昂,因为它需要服务器从集合或索引的开头开始遍历以获取偏移或跳过位置,然后再开始返回结果。随着偏移量的增加,cursor.skip() 将变得更慢并且更占用 CPU。
发生这种情况是因为skip 不适合 MapReduce 模型并且不是一个可以很好扩展的操作,您必须等待一个排序集合变得可用才能“切片”。现在limit(n) 听起来像一个同样糟糕的方法,因为它“从另一端”应用了类似的约束;但是,在应用排序后,引擎能够通过在遍历集合时仅在内存中保留每个分片的 n 个元素来优化流程。
另一种方法是使用基于范围的分页。检索第一页推文后,您知道最后一条推文的 created 值是什么,因此您只需用这个新值替换原来的 maxID:
db.tweets.find({created: {$lt: lastTweetOnCurrentPageCreated}).
sort({created: -1, username: 1}).
limit(50); //next page
像这样执行find 条件可以很容易地并行化。但是如何处理下一个以外的页面呢?您不知道第 5、10、20 页甚至上一页页的开始日期! @SergioTulentsev 建议 creative chaining of methods 但我主张在单独的 pages 集合中预先计算聚合字段的首尾范围;这些可以在更新时重新计算。此外,如果您对DateTime 不满意(注意性能备注)或担心重复值,您应该考虑compound indexes 时间戳+帐户绑定(因为用户不能同时发两次推文) ,甚至是两者的人工聚合:
db.pages.
find({pagenum: 3})
> {pagenum:3; begin:"01-01-2014@BillGates"; end:"03-01-2014@big_ben_clock"}
db.tweets.
find({_sortdate: {$lt: "03-01-2014@big_ben_clock", $gt: "01-01-2014@BillGates"}).
sort({_sortdate: -1}).
limit(50) //third page
使用聚合字段进行排序将“在折叠中”工作(尽管可能有更多的犹太洁食方法来处理这种情况)。这可以设置为a unique index,并在插入时纠正值,单个推文文档看起来像
{
_id: ...,
created: ..., //to be used in markup
user: ..., //also to be used in markup
_sortdate: "01-01-2014@BillGates" //sorting only, use date AND time
}