Javascript MongoDB分页的范围查询
我想在MongoDB上实现分页。对于我的范围查询,我考虑使用ObjectID:Javascript MongoDB分页的范围查询,javascript,mongodb,pagination,database,Javascript,Mongodb,Pagination,Database,我想在MongoDB上实现分页。对于我的范围查询,我考虑使用ObjectID: db.tweets.find({ _id: { $lt: maxID } }, { limit: 50 }) 但是,ObjectID的结构意味着“ObjectID值不代表严格的插入顺序”: ObjectId值的顺序和生成时间之间的关系在一秒钟内并不严格如果多个系统或单个系统上的多个进程或线程在一秒钟内生成值;ObjectId值并不表示严格的插入顺序。客户端之间的时钟偏移也会导致即使是值的非严格顺序,因为客户端驱动程
db.tweets.find({ _id: { $lt: maxID } }, { limit: 50 })
但是,ObjectID的结构意味着“ObjectID值不代表严格的插入顺序”:
ObjectId值的顺序和生成时间之间的关系在一秒钟内并不严格如果多个系统或单个系统上的多个进程或线程在一秒钟内生成值;ObjectId值并不表示严格的插入顺序。客户端之间的时钟偏移也会导致即使是值的非严格顺序,因为客户端驱动程序生成ObjectId值,而不是mongod进程
然后我考虑使用时间戳进行查询:
db.tweets.find({ created: { $lt: maxDate } }, { limit: 50 })
但是,不能保证日期是唯一的——很可能在同一秒钟内创建两个文档。这意味着分页时可能会丢失文档
是否有任何类型的范围查询可以为我提供更高的稳定性?如果您将查询限制在前一秒(或者不关心第二秒可能出现的奇怪情况),ObjectID应该足以进行分页。如果这还不足以满足您的需要,那么您需要实现一个像自动增量一样工作的ID生成系统 更新: 要查询前一秒的ObjectID,需要手动构造ObjectID 请参阅ObjectId的规范 尝试使用此表达式从mongos执行此操作
{ _id :
{
$lt : ObjectId(Math.floor((new Date).getTime()/1000 - 1).toString(16)+"ffffffffffffffff")
}
}
末尾的“f”用于最大化与时间戳无关的可能随机位,因为您正在执行小于查询
我建议在应用服务器上实际创建ObjectId时,而不是在mongos上创建ObjectId,因为如果您有很多用户,这种类型的计算可能会降低您的速度。推特“实际”时间戳(即推特的时间以及您希望它排序的标准)与推特“插入”时间戳不一样吗当然,这取决于您的应用程序,但很可能会出现这样的情况,即推特插入会被成批插入,或者最终以“错误”的顺序插入。因此,除非您在推特工作(并且能够访问以正确顺序插入的集合),您将无法仅依靠$natural
或ObjectID
进行排序逻辑
Mongo文件建议:
但是,使用skip时存在性能问题:
cursor.skip()
方法通常成本很高,因为它要求服务器从集合或索引的开头开始遍历,以获取偏移量或跳过位置,然后再开始返回结果。随着偏移量的增加,cursor.skip()
将变得更慢,CPU消耗也更大
之所以会出现这种情况,是因为skip
不适合MapReduce模型,并且不是一种可以很好地扩展的操作,您必须等待排序的集合可用后才能“切片”。现在听起来像是一种同样糟糕的方法,因为它“从另一端”应用了类似的约束;但是,通过应用排序,引擎能够在遍历集合时只保留每个碎片的内存n
元素,从而在一定程度上优化流程
另一种方法是使用基于范围的分页。检索推文的第一页后,您知道为最后一条推文创建的值是什么,因此您所要做的就是用这个新值替换原始的maxID
:
db.tweets.find({created: {$lt: lastTweetOnCurrentPageCreated}).
sort({created: -1, username: 1}).
limit(50); //next page
执行这样的find
条件可以很容易地并行化。但是如何处理下一个页面以外的页面?您不知道第5、10、20页的开始日期,甚至不知道上一页的开始日期!@SergioTulentsev建议,但我主张在单独的页面中预先计算聚合字段的最后一个范围此外,如果您不满意(注意性能备注)或关注重复值,则应考虑时间戳+帐户绑定(因为用户不能同时发出两次twitt),或者甚至是两个人工汇总:
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
使用聚合字段进行排序将“在折叠上”起作用(尽管可能有更多的犹太方式来处理这种情况)。这可以设置为在插入时更正值,单个tweet文档如下所示
{
_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});
这表示您希望tweets按\u id
值降序排序,并希望得到最近的50条。您的问题是,当当前结果集正在更改时,分页很棘手-因此,您不希望在下一页使用skip,而是希望记下结果集中最小的\u id
(第50个最新的\u id
值,然后使用以下内容进入下一页:
db.tweets.find( {_id : { "$lt" : <50th _id> } } ).limit(50).sort({"_id":-1});
db.tweets.find({u-id:{“$lt”:}}).limit(50).sort({u-id:-1});
这将为您提供下一条“最新”推文,而不会有新的推文干扰您的分页
完全没有必要担心\u id
值是否严格对应于插入顺序-它将足够接近99.999%,并且没有人真正关心次秒级别的推文,哪条推文排在第一位-你甚至可能会注意到推文频繁地按顺序显示推文,这并不重要
如果是关键性的,那么您必须使用相同的技术,但“tweet date”中的日期必须是时间戳,而不仅仅是日期。即使在同一毫秒内插入/更新了多个文档,即使来自多个客户端(这会生成ObjectId),以下方法仍然有效.对于相似性,在以下查询中,我将投影_id,lastModifiedDate
首先
db.tweets.find( {_id : { "$lt" : <50th _id> } } ).limit(50).sort({"_id":-1});
// import ObjectId from mongodb
let sortOrder = -1;
let query = []
if (prev) {
sortOrder = 1
query.push({title: 'findTitle', _id:{$gt: ObjectId('_idValue')}})
}
if (next) {
sortOrder = -1
query.push({title: 'findTitle', _id:{$lt: ObjectId('_idValue')}})
}
db.collection.find(query).limit(10).sort({_id: sortOrder})