1

假设我有一个复合索引 { a: 1, b: 1 }

该查询db.Collection.find( { b: 1 } )不使用此索引。查询优化器似乎没有选择此索引作为候选运行。

但是,如果您专门hint使用索引,则查询运行得更快并且nscan更低:

db.Collection.find( { b: 1 } ).hint( { a: 1, b: 1 } )

我的问题是,如果使用索引会产生更快的查询,为什么查询优化器b会单独忽略我的查询中的索引?

4

3 回答 3

2

从您链接到“复合索引”的页面:“复合索引支持对索引中字段的任何前缀的查询。” 索引有助于不是前缀的查询的情况是相当具体的,并且与值的分布有关a(我相信随着可能值的数量减少,它会做得更好a)。在这种情况下,最佳做法是不要尝试使用索引,因为这可能会使事情变慢。

在评论中,您建议在最坏的情况下它不应该太慢,但可以带来很大的改进。好吧,让我们尝试一些测试。我建立了一个包含 10^6 个文档的集合,其中每个文档i{a: i, b: i+1}. b在我的假设中,这是仅在使用 index时才进行查询的最坏情况{a: 1, b: 1}

对于查询

db.testing.find({b: 0}).explain()

我们发现它在大约 350 毫秒内扫描了 1,000,000 个文档(不足为奇)。对于未索引的查询来说还不错。现在,让我们提示该索引:

db.testing.find({b: 0}).hint("a_1_b_1").explain()

这次它只扫描了 954,546 个文档。我对 MongoDB 索引知之甚少,无法解释这一点。然而,这个稍微小一点的扫描花费了大约 2300 毫秒,或者是未索引查询的 6.5 倍。

所以是的,索引不佳的查询可能比未索引的查询更糟糕但这并不能完全回答您的问题 - 为什么查询优化器不解决这个问题?

查询优化器在第一次看到查询时并行运行不同的计划,并记住最好的计划以供将来查询(有时会重新评估)。但是,它只会尝试候选索引——即那些索引的某些非空前缀与查询的某些部分匹配的索引。当然,按照这个标准,{a: 1, b: 1}不是一个查询的候选索引就只是b.

我建议要么创建第二个索引{b: 1}(或至少使用该前缀),要么颠倒您已经拥有的索引的顺序(创建{b: 1, a: 1}然后删除旧索引)。

于 2013-06-18T05:57:08.730 回答
0

如果您有一个按“姓氏,名字”组织的电话簿,但您只有一个名字,您认为电话簿会帮助您找到您要搜索的人吗?

当您在 a、b 上有一个索引并且您在 b 上进行选择时,这就是您试图强制优化器执行的操作。这意味着对于 a 的每个值,它都需要查看 b 是否匹配。

在某些情况下,使用此索引可能比收集扫描更快的原因有很多。通常,它不是候选索引,您不应该将其用作加快对b.

当前版本的 MongoDB 查询优化器的工作方式是使用多个查询计划(所有候选索引加上集合扫描)尝试查询。无论哪个最快“获胜”,其他的都被终止,获胜的计划被缓存一段时间。如果您运行 `db.collection.find(...).explain(true),您实际上会看到它尝试过的所有“计划”。如果索引不被视为候选索引,那么它不会在此阶段处于混合状态 - 让查询使用它的唯一方法是显式“提示”它。

查询优化器将在下一个主要版本中进行更改,因此上述内容适用于 2.4 及更早版本中的世界状态。

于 2013-06-17T19:41:43.637 回答
0

复合索引通常用于前缀匹配查询或完全匹配查询。

显然,您的第一个查询不符合条件。您无需为此提供 hack。相反,您可以提示优化器使用 { a : 1, b : 1 } 索引

db.Collection.find({ b: 1 }).hint({ a:1, b:1 })
于 2013-06-17T18:16:25.990 回答