如何让mongodb使用不涉及某一字段的复合索引?

问题描述 投票:0回答:2

复合指数:

{
  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
是否存在,都应该返回。

mongodb mongodb-query aggregation-framework mongodb-indexes
2个回答
1
投票

复合索引,如您的

{ 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
,它们不形成前缀,因此不会使用索引。

您有两个选择:

  1. 将索引修改为

    { B: 1, C: 1, A: 1}

  2. 触发两个查询并连接它们的输出。

    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",
      }
     }
    ])
    

0
投票

我将采用与当前答案中的另一种方法不同的方法。虽然这个答案确实试图回答“how”,这是您直接询问的问题,但这个答案将进入行为的“why”。希望这些知识能帮助您更好地思考如何思考和处理这种情况。在此过程中,我们顺便展示了一种满足您原始请求的替代语法,尽管是否应该使用它并不明显。

在这种情况下数据库不想使用该索引的原因是这样做可能效率不高。因为索引是一个有序的数据结构,基于创建索引时字段在索引定义中的放置方式。由于您的索引首先按

A
排序,并且您的查询将检索文档,无论它们的
A
值如何,这意味着:

  1. 与您的查询相关的项目分散在整个索引中。
  2. 数据库最终可能会做大量的工作,试图在索引中跳转来尝试找到这些相关条目。

相比之下,查询使用其主键的索引包含索引的一个部分中的所有相关内容。数据库知道这一点,这正是它们在支持选择性查询方面如此有效的原因。

为了进一步探索这一点,我们可以使用这种替代语法来触发索引使用(或至少是资格):

{ 
    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

字段具有高基数的情况是否实际上会更快。 简而言之:在这种情况下,您可能比数据库更能了解此特定操作的最有效方法是什么。如果是这样,并且如果拥有其他索引没有意义,那么您可能必须通过更改查询或提示索引之类的操作来帮助数据库,就像我们在这里所做的那样。

    

© www.soinside.com 2019 - 2024. All rights reserved.