复合指数:
{
A: 1,
B: 1,
C: 1
}
查询:
col.aggregate([
{
$match: {
B: "ssome_value",
C: "some_other_value,
}
}
])
查询不涉及
A
,所以不使用复合索引。我可以这样做来欺骗它使用索引:
col.aggregate([
{
$match: {
A: { $exists: true },
B: "ssome_value",
C: "some_other_value,
}
}
])
但问题是结果不准确。它应该返回所有文档,而不管
A
,而不仅仅是它存在。无论A
是否存在,都应该返回。
复合索引,如您的
{ A: 1, B: 1, C: 1}
,仅当其字段的前缀出现在匹配条件中时才会使用。例如:{ A: 'some_value' }
、{ A: 'some_value', B: 'some_value' }
、{ A: 'some_value', B: 'some_value', C: 'some_value' }
。您的过滤表达式仅包含 B
和 C
,它们不形成前缀,因此不会使用索引。
您有两个选择:
将索引修改为
{ B: 1, C: 1, A: 1}
。
触发两个查询并连接它们的输出。
col.aggregate([
{
$match: {
A: { $exists: true },
B: "ssome_value",
C: "some_other_value",
}
}
])
col.aggregate([
{
$match: {
A: { $exists: false },
B: "ssome_value",
C: "some_other_value",
}
}
])
我将采用与当前答案中的另一种方法不同的方法。虽然这个答案确实试图回答“how”,这是您直接询问的问题,但这个答案将进入行为的“why”。希望这些知识能帮助您更好地思考如何思考和处理这种情况。在此过程中,我们顺便展示了一种满足您原始请求的替代语法,尽管是否应该使用它并不明显。
在这种情况下数据库不想使用该索引的原因是这样做可能效率不高。因为索引是一个有序的数据结构,基于创建索引时字段在索引定义中的放置方式。由于您的索引首先按
A
排序,并且您的查询将检索文档,无论它们的 A
值如何,这意味着:
相比之下,查询使用其主键的索引包含索引的一个部分中的所有相关内容。数据库知道这一点,这正是它们在支持选择性查询方面如此有效的原因。
为了进一步探索这一点,我们可以使用这种替代语法来触发索引使用(或至少是资格):
{
A: { '$gte': MinKey() },
B: 'ssome_value',
C: 'some_other_value'
}
在这里,我们只是告诉系统我们想要使用
A
BSON 类型(另请参阅规范)获得具有任何
Min key
值的文档,例如:
> db.foo.aggregate([{$match:{A: { '$gte': MinKey() }, B: 'ssome_value', C: 'some_other_value' }}])
[
{ _id: 0, B: 'ssome_value', C: 'some_other_value' },
{ _id: 2, A: 123, B: 'ssome_value', C: 'some_other_value' },
{ _id: 1, A: true, B: 'ssome_value', C: 'some_other_value' }
]
此语法在指定索引上生成以下边界:
indexBounds: {
A: [ '[MinKey, MaxKey]' ],
B: [ '["ssome_value", "ssome_value"]' ],
C: [ '["some_other_value", "some_other_value"]' ]
}
不过这里要小心。仅仅因为您可以这样做并不一定意味着您应该。我上面提到优化器默认情况下避免使用这个索引是有充分理由的。该索引可能比进行完整集合扫描更有效。但也有可能使用索引会更糟! 这主要取决于集合中有多少个不同的
A
值。如果
A
的值很少,索引可能会很有帮助。仅使用上面 A
的三个值添加更多(不匹配)文档,我们可以观察到以下内容: db.foo.distinct('A').length
3
> db.foo.count()
100
> db.foo.aggregate([{$match:{A: { '$gte': MinKey() }, B: 'ssome_value', C: 'some_other_value' }},{$count:"count"}])
[ { count: 3 } ]
> db.foo.aggregate([{$match:{A: { '$gte': MinKey() }, B: 'ssome_value', C: 'some_other_value' }}]).explain("executionStats").executionStats
{
nReturned: 3,
totalKeysExamined: 4,
totalDocsExamined: 3,
...
但另一方面,请考虑
A
的值在集合中的所有文档中是唯一的情况。这里数据库将扫描完整索引:
> db.foo.distinct('A').length
100
> db.foo.count()
100
> db.foo.aggregate([{$match:{A: { '$gte': MinKey() }, B: 'ssome_value', C: 'some_other_value' }},{$count:"count"}])
[ { count: 3 } ]
> db.foo.aggregate([{$match:{A: { '$gte': MinKey() }, B: 'ssome_value', C: 'some_other_value' }}]).explain("executionStats").executionStats
{
nReturned: 3,
totalKeysExamined: 100,
totalDocsExamined: 3,
虽然扫描索引通常比直接扫描集合更快,但还要考虑数据库几乎每次都从根节点重新遍历索引(而不是顺序扫描叶节点)。我们可以通过查看
IXSCAN
阶段内的指标来判断:
indexBounds: {
A: [ '[MinKey, MaxKey]' ],
B: [ '["ssome_value", "ssome_value"]' ],
C: [ '["some_other_value", "some_other_value"]' ]
},
keysExamined: 100,
seeks: 98,
使索引对查询最有效,因此尚不清楚这种方法对于
A
字段具有高基数的情况是否实际上会更快。 简而言之:在这种情况下,您可能比数据库更能了解此特定操作的最有效方法是什么。如果是这样,并且如果拥有其他索引没有意义,那么您可能必须通过更改查询或提示索引之类的操作来帮助数据库,就像我们在这里所做的那样。