问题:如何在 MongoDB 中对$or$in查询进行排序?

这是这个问题的后续行动 - 请参阅上下文。

这个问题涉及链接问题的几个特殊情况 - 即使用$in$or运算符时 MongoDB 中的排序如何工作,以及如何确保使用索引进行排序与内存排序。

$in:

例如,假设我们有一个集合,其中文档结构是

{a: XXX, b: XXX}

...我们在ab上有一个复合索引,并希望运行查询

{a: {$in: [4, 6, 2, 1, 3, 10]}, b: {$gt: 1, $lt: 6}}

如果它在ab上,排序将如何进行?$in是一种相等运算符,但在我看来,即使这样,在b上进行带有索引的排序也是不可能的。我认为,只有首先对$in值数组进行排序,才有可能使用索引对a进行排序 - 但我不知道 MongoDB 是否这样做。

$或:

由于$or查询(IIUC)被作为多个查询处理,并且可能使用它们各自的索引进行排序,排序后的结果是否以某种方式合并,或者$or是否强制对所有结果进行内存排序?如果是前者,这个过程的时间复杂度是多少?

解答

注意: 此答案基于 MongoDB 3.2.4。

值得发现explain()在 MongoDB 中的使用。查询的explain()输出(例如db.collection.explain().find(...))允许您检查查询中使用了哪个索引,并且使用db.collection.explain('executionStats')还会显示由于内存中 zwz1 的限制,查询是成功还是失败。

$in

可以将$in查询视为一系列相等查询。例如,{a: {$in: [1,3,5]}}可以被认为是{a:1}, {a:3}, {a:5}。 MongoDB 在继续查询之前会对$in数组进行排序,因此{$in: [3,5,1]}{$in: [1,3,5]}没有什么不同。

假设集合的索引为

{a:1, b:1}
  • a排序
db.coll.find({a: {$in: [1,3,5]}}).sort({a:1})

MongoDB 将能够使用{a:1,b:1}索引,因为这个查询可以被认为是{a:1}, {a:3}, {a:5}查询的联合。按{a:1}排序允许使用索引前缀,因此 MongoDB 不需要执行内存排序。

同样的情况也适用于查询:

db.coll.find({a: {$in: [1,3,5]} ,b:{$gte:1, $lt:2}}).sort({a:1})

由于sort({a:1})也使用索引前缀(在这种情况下为a),因此不需要内存中的SORT阶段。

  • b排序

a排序相比,这是一个更有趣的案例。例如:

db.coll.find({a: {$in: [1,3,5]}}).sort({b:1})

此查询的explain()输出将有一个称为SORT_MERGE的阶段。请记住,查询的find()部分可以被认为是{a:1}, {a:3}, {a:5}

由于{a:1,b:1}索引的性质,查询db.coll.find({a:1}).sort({b:1})不需要内存中的SORT阶段:即 MongoDB 在满足a上的相等参数后,可以简单地遍历(排序的)索引并返回按b排序的文档。例如,对于每个a,有很多b由于索引的原因已经按b排序。

使用$in,整体查询可以认为是:

  • zoz100077

*db.coll.find({a:3}).sort({b:1})

*db.coll.find({a:5}).sort({b:1})

  • 取上面的单个查询结果,并使用b的值进行合并。查询_不需要内存排序阶段_因为各个查询结果已经按b排序。 MongoDB 只需要将(已排序的)子查询结果合并为一个结果。

同样,查询

db.coll.find({a: {$in: [1,3,5]} ,b:{$gte:1, $lt:2}}).sort({b:1})

也使用了SORT_MERGE阶段,与上面的查询非常相似。不同之处在于,对于每个a(由于索引{a:1,b:1}将按b排序),单个查询基于_a range of_b(而不是_every_b)输出文档。因此,查询不需要内存中的排序阶段。

$或

对于要使用索引的$or查询,$or表达式中的每个子句都必须具有与之关联的索引。如果满足此要求,则查询可以像使用$in查询一样使用SORT_MERGE阶段。例如:

db.coll.explain().find({$or:[{a:1},{a:3},{a:5}]}).sort({b:1})

将具有与上面的$in示例几乎相同的查询计划、索引使用和SORT_MERGE阶段。本质上,查询可以被认为是:

  • db.coll.find({a:1}).sort({b:1})

  • db.coll.find({a:3}).sort({b:1})

  • db.coll.find({a:5}).sort({b:1})

  • 取上面的单个查询结果,并使用b的值进行合并。

就像之前的$in示例一样。

但是,此查询:

db.coll.explain().find({$or:[{a:1},{b:1}]}).sort({b:1})

不能使用任何索引(因为我们没有{b:1}索引)。此查询将导致集合扫描,因此_将有一个内存排序阶段_,因为没有使用索引。

但是,如果我们创建索引{b:1},查询将按如下方式进行:

  • db.coll.find({a:1}).sort({b:1})

  • db.coll.find({b:1}).sort({b:1})

  • 获取上面的单个查询结果,并使用b的值执行合并(由于索引{a:1,b:1}{b:1},它已经在两个子查询中排序)。

MongoDB 将结合{a:1}{b:1}查询的结果,并对结果进行合并。合并过程是线性时间,例如O(n).

总之,在$or查询中,每个词都必须有一个索引,包括sort()阶段。否则,MongoDB 将不得不执行内存排序。

Logo

MongoDB社区为您提供最前沿的新闻资讯和知识内容

更多推荐