【问题标题】:How does sorting work with `$or` and `$in` queries in MongoDB?排序如何处理 MongoDB 中的 `$or` 和 `$in` 查询?
【发布时间】:2016-07-29 04:39:32
【问题描述】:

这是this question 的后续行动 - 请参阅上下文。

这个问题涉及链接问题的几个特殊情况 - 即使用 $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 是否强制对所有结果进行内存排序?如果是前者,这个过程的时间复杂度是多少?

【问题讨论】:

  • 到底是什么问题?您是在问$or$in 如何影响排序的“索引”选择?还是你在问别的?您确实意识到“查询”阶段也可以使用一个(甚至是一对带有交集的)索引,而“排序”阶段也可以使用不同的索引。
  • @NeilLunn - 嘿,尼尔,这个问题在这一点上有一个答案,所以我认为你评论的第一部分是没有实际意义的,但关于后一部分,根据this answer 它实际上并不是可以在一个索引上查询并在完全不同的索引上排序。
  • “不可能” 实际上并不正确,因为 “可能” 与查询优化器认为的内容之间存在明显差异“最佳”。但是下面给出的答案对出现的常见情况给出了相当合理的解释。
  • @NeilLunn 我对链接答案的解释是,无法对不同的索引、周期进行查询和排序,优化器永远无法做到这一点。特别是我正在查看后续问题更新部分第 1 部分:“您不能有两个单独的索引 {a:1} 和 {b:1},并期望 {a: 1} 索引用于相等部分,{b:1} 索引用于排序部分。在这种情况下,MongoDB 将选择两个索引之一。"
  • 好吧,无论您选择哪种方式看待它,您的“解释”或“答案”都不完全正确。

标签: mongodb sorting indexing time-complexity


【解决方案1】:

注意:此答案基于 MongoDB 3.2.4。

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

$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} 排序允许使用index prefix,因此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 可以简单地遍历(排序的)索引并返回按@ 排序的文档987654358@ 满足a 上的相等参数后。例如,对于每个a,有很多b由于索引已经被b排序。

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

    • 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 的值执行合并。查询不需要内存中的排序阶段,因为各个查询结果已经按b 排序。 MongoDB 只需要将(已排序的)子查询结果合并为一个结果。

    同样,查询

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

    也使用SORT_MERGE 阶段,与上面的查询非常相似。不同之处在于,对于每个 a(将按b 由于索引 {a:1,b:1})。因此,查询不需要内存中的排序阶段。

$或

对于使用索引的$or 查询,every clause in the $or expression must have an index associated with it。如果满足此要求,则查询可以使用SORT_MERGE 阶段,就像$in 查询一样。例如:

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 将不得不执行内存排序。

【讨论】:

  • 很好的答案,谢谢。也感谢之前的回答,非常感谢。
  • 嘿凯文。我发现在db.col.find({a: {$in: [val1, va2]}}).sort({b: 1}) 的情况下,如果我(极大地)增加$ina 的数量或可能性,Mongo 不会执行SORT_MERGE,而是回退到粗略的内存SORT .这在stackoverflow.com/questions/46318304/… 有详细说明,有什么建议吗?
  • @CyrilCHAPON 我已经对你的问题发表了评论。
猜你喜欢
  • 2013-07-07
  • 2019-12-16
  • 1970-01-01
  • 1970-01-01
  • 2021-07-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多