继续对复合索引进行查询(分页)

时间:2014-02-25 05:27:54

标签: mongodb mongodb-query

我有一个(希望很快)关于复合索引的MongoDB查询的问题。

假设我有一个数据集(例如,评论),我想按分数降序排序,然后是日期:

{ "score" : 10, "date" : ISODate("2014-02-24T00:00:00.000Z"), ...}
{ "score" : 10, "date" : ISODate("2014-02-18T00:00:00.000Z"), ...}
{ "score" : 10, "date" : ISODate("2014-02-12T00:00:00.000Z"), ...}
{ "score" : 9, "date" : ISODate("2014-02-22T00:00:00.000Z"), ...}
{ "score" : 9, "date" : ISODate("2014-02-16T00:00:00.000Z"), ...}
...

到目前为止,我的理解是我可以创建一个复合索引来支持此查询,该查询看起来像{"score":-1,"date":-1}。 (为了清楚起见,我没有在索引中使用日期,而是使用ObjectID来获取唯一的,大致基于时间的顺序)

现在,我想支持通过评论进行分页。第一页很简单,我可以在光标的末尾添加.limit(n)选项。我正在努力的是继续搜索。

我一直在提到Kristina Chodorow的 MongoDB:The Definitive Guide 。在本书中,Kristina提到在大型数据集上使用skip()不是很有效,并建议对上次看到的结果(例如,最后看到的日期)使用范围查询。

我想要做的是执行一个作用于两个字段的范围查询,但将第二个字段视为第二个字段的第二个字段(就像索引已排序一样。)因为我的复合索引已经按顺序排序我想,似乎应该通过指向索引中的特定元素并以排序顺序遍历它来跳转到搜索中。但是,从我对MongoDB中的查询的理解(基本上是初步的)来看,这似乎是不可能的。

据我所知,我有三个选择:

  1. 无论如何使用skip()
  2. 使用$或查询或两个不同的查询:{$or : [{"score" : lastScore, "date" : { $lt : lastDate}}, {'score' : {$lt : lastScore}]}
  3. 使用$max特殊查询选项
  4. 3号对我来说似乎最接近理想,但参考文本指出“你通常应该使用”$ lt“而不是”$ max“'。

    总结一下,我有几个问题:

    1. 有没有办法执行我描述的操作,我可能错过了? (跳转到索引并按排序顺序遍历它)
    2. 如果没有,我描述的三个选项(或任何我忽略的选项)中,(一般来说)会在复合指数下给出最一致的表现吗?
    3. 为什么在大多数情况下,$ lt首选超过$ max?
    4. 提前感谢您的帮助!

1 个答案:

答案 0 :(得分:2)

另一种选择是将scoredate存储在子文档中,然后将子文档编入索引。例如:

{
  "a" : { "score" : 9,
          "date" : ISODate("2014-02-22T00:00:00Z") },
  ...
}

db.foo.ensureIndex( { a : 1 } )

db.foo.find( { a : { $lt : { score : lastScore,
                             date: lastDate } } } ).sort( { a : -1 } )

使用这种方法,您需要确保BSON子文档中的字段始终以相同的顺序存储,否则查询将与您的预期不匹配,因为索引键比较是整个BSON子目录的二进制比较文档。

我会使用$max指定上限,并与$hint一起使用以确保数据库使用您想要的索引。 $lt通常优先于$max的原因是因为$max使用指定的索引边界选择索引。这意味着:

  • 所选择的指数未必是最佳选择。
  • 如果在具有不同排序顺序的相同字段上存在多个索引,则索引的选择可能不明确。

以上几点详细介绍here

最后一点:max相当于$lte,而不是$lt,因此使用此方法进行分页时,您需要跳过第一个返回的文档以避免输出相同内容文件两次。