MongoDB分页的范围查询

 mobiledu2502914617 发布于 2023-02-06 10:38

我想在MongoDB上实现分页.对于我的范围查询,我考虑过使用ObjectID:

db.tweets.find({ _id: { $lt: maxID } }, { limit: 50 })

但是,根据文档,ObjectID的结构意味着"ObjectId值不代表严格的插入顺序":

ObjectId值的顺序与生成时间之间的关系在一秒内不严格.如果单个系统上的多个系统或多个进程或线程在一秒钟内生成值; ObjectId值不代表严格的插入顺序.客户端之间的时钟偏差也会导致非严格的排序,即使是值,因为客户端驱动程序生成ObjectId值,而不是mongod进程.

然后我考虑用时间戳查询:

db.tweets.find({ created: { $lt: maxDate } }, { limit: 50 })

但是,不能保证日期是唯一的 - 很可能在同一秒内创建两个文档.这意味着在分页时可能会错过文档.

是否有任何类型的远程查询可以为我提供更多稳定性?

2 个回答
  • 推文"实际"时间戳(即推文的时间和您希望它排序的标准)不会与推文"插入"时间戳(即添加到本地集合的时间)不同.当然,这取决于您的应用程序,但是可能会出现推文插入可能被批处理或最终以"错误"顺序插入的情况.因此,除非您在Twitter工作(并且能够以正确的顺序访问集合),否则您将无法仅仅依赖于$naturalObjectID用于排序逻辑.

    Mongo文档建议skiplimit分页:

    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
    }
    

    2023-02-06 10:40 回答
  • 尽管你的分页语法错误,但使用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经常显示推文秩序,它不是那么关键.

    如果它关键的,那么你将不得不使用相同的技术但使用"推文日期",其中该日期必须是时间戳,而不仅仅是日期.

    2023-02-06 10:40 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有