我想在MongoDB上实现分页.对于我的范围查询,我考虑过使用ObjectID:
db.tweets.find({ _id: { $lt: maxID } }, { limit: 50 })
但是,根据文档,ObjectID的结构意味着"ObjectId值不代表严格的插入顺序":
ObjectId值的顺序与生成时间之间的关系在一秒内不严格.如果单个系统上的多个系统或多个进程或线程在一秒钟内生成值; ObjectId值不代表严格的插入顺序.客户端之间的时钟偏差也会导致非严格的排序,即使是值,因为客户端驱动程序生成ObjectId值,而不是mongod进程.
然后我考虑用时间戳查询:
db.tweets.find({ created: { $lt: maxDate } }, { limit: 50 })
但是,不能保证日期是唯一的 - 很可能在同一秒内创建两个文档.这意味着在分页时可能会错过文档.
是否有任何类型的远程查询可以为我提供更多稳定性?
推文"实际"时间戳(即推文的时间和您希望它排序的标准)不会与推文"插入"时间戳(即添加到本地集合的时间)不同.当然,这取决于您的应用程序,但是可能会出现推文插入可能被批处理或最终以"错误"顺序插入的情况.因此,除非您在Twitter工作(并且能够以正确的顺序访问集合),否则您将无法仅仅依赖于$natural
或ObjectID
用于排序逻辑.
Mongo文档建议skip
和limit
分页:
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建议对方法进行创造性链接,但我主张在单独的pages
集合中预先计算聚合字段的第一个最后范围; 这些可以在更新时重新计算.此外,如果您不满意DateTime
(注意性能评论)或关注重复值,您应该考虑时间戳+帐户关系上的复合索引(因为用户不能同时发送两次推文),甚至是两者的人工骨料:
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
使用聚合字段进行排序将"起作用"(尽管可能有更多犹太方法来处理这种情况).这可以设置为唯一索引,其值在插入时更正,单个推文文档看起来像
{ _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 }
尽管你的分页语法错误,但使用ObjectId()是完全没问题的.你要:
db.tweets.find().limit(50).sort({"_id":-1});
这表示你希望推文按_id
值按降序排序,你想要最新的50.你的问题是当前结果集发生变化时分页很棘手 - 所以不要使用跳过下一页,你要制作注意_id
结果集中的最小值(第50个最新_id
值,然后得到下一页:
db.tweets.find( {_id : { "$lt" : <50th _id> } } ).limit(50).sort({"_id":-1});
这将为您提供下一个"最新"的推文,而不会有新的推文随着时间的推移搞乱您的分页.
绝对没有必要担心_id
价值是否严格对应于插入顺序 - 它将足够接近99.999%,并且没有人真正关心推文首先出现的亚秒级别 - 您甚至可能会注意到Twitter经常显示推文秩序,它不是那么关键.
如果它是关键的,那么你将不得不使用相同的技术但使用"推文日期",其中该日期必须是时间戳,而不仅仅是日期.