Mongo三重复合索引

3
如果你有一个双复合索引 { a : 1, b : 1},在查询只涉及 b 的情况下(即您不能“跳过”a),我认为索引不会被使用。但是,如果您单独查询a,则该索引将被使用。
然而,给定一个三重复合索引{ a : 1, b: 1, c: 1},当您查询a和c时(即您可以在查询中“跳过”b),我的解释命令显示该索引被使用了。
Mongo如何在查询ac时使用abc索引,这种情况下索引的效果如何?
背景:
我的用例是有时我想查询a、b和c,有时我想查询a和c。现在我应该只在a、b和c上创建一个索引还是在a、c上创建一个,在a、b和c上再创建一个呢?
(在c是具有很好可选择性的多键索引情况下,创建a、c、b索引是没有意义的。)
2个回答

4
底线 / 简而言之: 如果查询等式或不等式时,可以“跳过”索引b,但是在对c进行排序时不行。

这是一个非常好的问题。不幸的是,我没有找到任何权威的答案来详细回答这个问题。我相信在过去几年中,此类查询的性能有所提高,因此我不会信任有关此主题的旧材料。

整个问题相当复杂,因为它取决于您的索引选择性以及您是否查询等式、不等式和/或排序,所以explain()是您唯一的朋友,但是这里有一些我发现的东西:

注意:接下来的内容是实验结果、推理和猜测的混合物。我可能过分解释了Kyle的比喻,并且我甚至可能完全错了(并且不幸的是,我的测试结果与我的推理大致匹配)。

很明显可以使用A的索引,这取决于A的选择性,肯定非常有帮助。跳过B可能会有些棘手,也可能不会。让我们将其保持类似于Kyle's cookbook example

French
    Beef
        ...
    Chicken
        Coq au Vin
        Roasted Chicken
    Lamb
        ...
    ...

如果你现在让我找一道名为“香槟牛排”的法国菜肴,我可以使用索引A,但由于我不知道该菜肴的成分,我将不得不扫描A中的所有菜肴。另一方面,我知道每个类别的菜肴列表都是通过索引C排序的,因此我只需要查找以“Cha”开头的字符串所在的每个成分列表。如果有50种成分,则我需要进行50次查找,而不是扫描每一个法国菜肴!

在我的实验中,这个数字比b中不同值的数量小得多:它似乎从未超过2。然而,我只使用了单个集合进行测试,它可能与b-index的选择性有关。

如果你让我给你一个按字母顺序排序的所有法国菜肴列表,那么我就麻烦了。现在C上的索引毫无用处,我必须合并排序所有这些索引列表。我将不得不扫描每个元素来执行此操作。
这在我的测试中得到了体现。以下是一些简化的结果。原始集合包含日期时间、整数和字符串,但为了保持简单,现在全部都是整数。
基本上,只有两类查询:那些nscanned <= 2 * limit的查询,以及必须扫描整个集合(120k文档)的查询。索引是{a, b, c}
// fast (range query on c while skipping b)
> db.Test.find({"a" : 43, "c" : { $lte : 45454 }});
// slow (sorting)
> db.Test.find({"a" : 43, "c" : { $lte : 45454 }}).sort({ "c" : -1});
> db.Test.find({"a" : 43, "c" : { $lte : 45454 }}).sort({ "b" : -1}); 

// fast (can sort on c if b included in the query)
> db.Test.find({"a" : 43, "b" : 7887, "c" : { $lte : 45454 }}).sort({ "c" : -1});

// fast (older tutorials claim this is slow)
> db.Test.find({"a" : {$gte : 43}, "c" : { $lte : 45454 }});

你的里程可能会有所不同。

谢谢,鉴于缺乏权威参考资料,这可能是我们能得到的最好信息了。在我的具体情况下,B是一个低选择性列,并且我正在对C进行“以...开始”的查询。因此,根据您的答案,我的ABC指数对于在AC上进行查询几乎与AC指数一样好?我知道情况可能会有所不同,所以现在我可能会同时使用AC和ABC指数... - Zaid Masud
只要不按C排序,我猜它几乎和AC-index一样好,但是explain()可以告诉你确切的情况。请注意nscanned,它不应该比n大太多。 - mnemosyn

1

您可以将在A和C上查询视为在A上查询的特殊情况(此时将使用索引)。使用索引比加载整个文档更有效率。

假设您想要获取所有A在7到13之间,且C在5到8之间的文档。

如果只有A上的索引:数据库可以使用索引选择A在7到13之间的文档,但是为了确保C也在5到8之间,它还必须检索相应的文档。

如果A、B和C都有索引:数据库可以使用索引选择A在7到13之间的文档。由于C的值已经存储在索引记录中,因此它可以确定相应的文档是否也符合C的条件,而无需检索这些文档。因此,您可以避免磁盘读取,提高性能。


换句话说,您是在说当您查询AC时,ABC索引与仅针对A的索引没有区别吗? - Zaid Masud
它更好,因为它可以从索引本身过滤掉C中不需要的值,而无需读取文档。 - Eduardo
-1 @Eduardo,我不太明白你的推理。它怎么能使用索引的C部分呢?认为索引的目标是不“加载整个文档”是非常误导人的。事实上,你应该从需要扫描的文档数量(nscan)的角度来考虑它。 - Zaid Masud
如果您的查询涉及A和C,并且您只有一个关于A的索引,那么数据库将不得不读取所有与查询匹配的A文档,以确定它们的C值是否也与查询匹配。由于索引通常比集合小得多,仅使用索引(带有A和C)来确定哪些文档与查询匹配将导致更少的磁盘读取,从而提高性能。 - Eduardo
抱歉,但Mongo索引不是这样工作的。请阅读http://kylebanker.com/blog/2010/09/21/the-joy-of-mongodb-indexes/。由于C按B的分组排序,并且不能单独排序,因此在您的答案中需要显示如何使用C。 - Zaid Masud
如果索引的C部分本身没有排序,也没有关系。主要搜索是在A上完成的。如果A部分匹配,则必须分析C部分。为了分析C,最好将其放在索引中,而不是读取相应的文档。 - Eduardo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接