MongoDB:复合索引决策

时间:2013-12-29 19:07:26

标签: php mongodb

我最近不得不在MongoDB上优化某些查询集,并遇到这个特殊问题:

说我的查询与AB匹配,然后在C上进行范围选择,并在D上排序,因此在shell中看起来像:

db.collection.find({ A: 'something', B: 'something-else', C: { $gt: 100 } })
             .sort({ D: -1 }).limit(10)

去年我read a post讨论了为这种情景创建索引的基本规则:

  1. 精确值匹配字段首先
  2. 排序字段排在第二位
  3. 范围搜索($ in,$ gt等)字段最后
  4. 他们的树解释看起来很合理所以我继续创建了一个索引:

    db.collection.ensureIndex({ A:1, B:1, D:-1, C:-1 })
    

    现在出现问题: mongodb决定BasicCursor优于此索引。如果我hint它的完整索引(并且更快),但这样做需要对我们的代码库进行相当多的更改,所以我们尽量避免这种情况。


    我的问题是:

    1. 当我的查询包含所有4个字段时,为什么mongodb查询优化器决定{ A:1, E:-1 }{ D:-1 }甚至BasicCursor优于{ A:1, B:1, D:-1, C:-1 }

    2. { A:1, D:-1 }是多余的,mongo docs确实说使用部分索引的效率较低?

    3. 此外,我们还有以下查询:

      db.collection.find({ A: { $in : ['str1','str2'] }, B: 'something', C: { $gt: 100 } })
                   .sort({ D: -1 }).limit(10)
      

      为了有效地查询它,我们是否需要一个额外的索引,如下所示?坦率地说,我不确定MongoDB查询优化器将如何处理它们。

      db.collection.ensureIndex({ B:1, D:-1, C:-1, A:1 })
      

      这些是我的查询的解释,有或没有提示。

      原来它是默认为{ A:1, E:-1 }而不是{ A:1, D:-1 },这似乎更奇怪,因为我们没有在字段E上查询。

      我删除了{ A:1, E:-1 }上的索引,现在解释告诉我默认为{ D:-1 },所以我也放弃了它,现在MongoDB开始使用BasicCursor ...它没有似乎既不喜欢我的完整索引也不喜欢A:1, D:-1索引(尽管暗示效果要好得多)。

      这感觉很奇怪。

2 个答案:

答案 0 :(得分:1)

这种“不寻常”的唯一原因是,如果您的数据分布恰好使得BasicCursor实际完成查询(即找到所有匹配的文档)比索引查询更快。 “部分”索引也是如此。

使用您的数据结构作为示例的特定情况是,如果a在集合的开头具有相对较少的不同值,并且b具有极低的基数(即,非常少的不同值,例如一个或一个少数)然后按顺序扫描集合或使用“效率较低”的索引将显示与使用理论上“理想”索引相同或更好的性能。

这是一个示例,其中前1000个文档的a = 1且b = 2 - 后来的文档的分布非常不同。

> db.compound4.find({a:1, b:2, d:{$lt:100}}).sort({c:-1}).limit(10).explain(true)
{
    "cursor" : "BtreeCursor a_1",
    "isMultiKey" : false,
    "n" : 10,
    "nscannedObjects" : 18,
    "nscanned" : 18,
    "nscannedObjectsAllPlans" : 46,
    "nscannedAllPlans" : 56,
    "scanAndOrder" : true,
    "indexOnly" : false,
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 0,
    "indexBounds" : {
        "a" : [
            [
                1,
                1
            ]
        ]
    },
    "allPlans" : [
        {
            "cursor" : "BtreeCursor a_1",
            "n" : 18,
            "nscannedObjects" : 18,
            "nscanned" : 18,
            "indexBounds" : {
                "a" : [
                    [
                        1,
                        1
                    ]
                ]
            }
        },
        {
            "cursor" : "BtreeCursor a_1_b_1_c_1_d_1 reverse",
            "n" : 10,
            "nscannedObjects" : 10,
            "nscanned" : 20,
            "indexBounds" : {
                "a" : [
                    [
                        1,
                        1
                    ]
                ],
                "b" : [
                    [
                        2,
                        2
                    ]
                ],
                "c" : [
                    [
                        {
                            "$maxElement" : 1
                        },
                        {
                            "$minElement" : 1
                        }
                    ]
                ],
                "d" : [
                    [
                        100,
                        -1.7976931348623157e+308
                    ]
                ]
            }
        },
        {
            "cursor" : "BasicCursor",
            "n" : 18,
            "nscannedObjects" : 18,
            "nscanned" : 18,
            "indexBounds" : {

            }
        }
    ]
}

由于复合索引很大,遍历所需的时间比较小的部分索引要长,并且由于“b”的选择性不是很好(即非常差),因此会使查询计划落后。

答案 1 :(得分:0)

使用explain(true)运行原始查询后,mongodb查询优化器背后的推理更加清晰。

这是因为字段Cint字段上的范围搜索,并且预期具有高选择性,但在搜索开始时没有很多匹配结果(特别是当结果按顺序排序时) D和降序,这是一个日期字段。)

mongodb查询优化器后面的base logic似乎是:给定相同数量的n结果,请使用最小nscanned的计划(索引)(如果nscanned = n)的即可。在小n的情况下,例如。 limit(10),优化器可能无法使用我们为其计划的最有效的索引。

就我而言,事实证明索引{ D:-1 }BasicCursor都胜过此类测试的完整索引。所以这个谜就解决了。

PS:出于好奇,这些是我的测试数据集的explain(true)输出:

希望这有助于某人:)

相关问题