我最近不得不在MongoDB上优化某些查询集,并遇到这个特殊问题:
说我的查询与A
和B
匹配,然后在C
上进行范围选择,并在D
上排序,因此在shell中看起来像:
db.collection.find({ A: 'something', B: 'something-else', C: { $gt: 100 } })
.sort({ D: -1 }).limit(10)
去年我read a post讨论了为这种情景创建索引的基本规则:
他们的树解释看起来很合理所以我继续创建了一个索引:
db.collection.ensureIndex({ A:1, B:1, D:-1, C:-1 })
现在出现问题: mongodb决定BasicCursor优于此索引。如果我hint
它的完整索引(并且更快),但这样做需要对我们的代码库进行相当多的更改,所以我们尽量避免这种情况。
我的问题是:
当我的查询包含所有4个字段时,为什么mongodb查询优化器决定{ A:1, E:-1 }
,{ D:-1 }
甚至BasicCursor优于{ A:1, B:1, D:-1, C:-1 }
。
{ A:1, D:-1 }
是多余的,mongo docs确实说使用部分索引的效率较低?
此外,我们还有以下查询:
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
索引(尽管暗示效果要好得多)。
这感觉很奇怪。
答案 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查询优化器背后的推理更加清晰。
这是因为字段C
是int
字段上的范围搜索,并且预期具有高选择性,但在搜索开始时没有很多匹配结果(特别是当结果按顺序排序时) D
和降序,这是一个日期字段。)
mongodb查询优化器后面的base logic似乎是:给定相同数量的n
结果,请使用最小nscanned
的计划(索引)(如果nscanned = n)的即可。在小n的情况下,例如。 limit(10)
,优化器可能无法使用我们为其计划的最有效的索引。
就我而言,事实证明索引{ D:-1 }
和BasicCursor
都胜过此类测试的完整索引。所以这个谜就解决了。
PS:出于好奇,这些是我的测试数据集的explain(true)
输出:
没有C
字段符合我们的范围搜索:http://pastebin.com/5ibFDs44
与我们的范围搜索匹配的每个C
字段:http://pastebin.com/4Y35dMXM
希望这有助于某人:)