MongoDB $project和索引使用

5
我会用聚合框架对一些数据进行分组。观察到当使用$project管道阶段时,它会阻止后续的$match使用索引。我在字段“timestamp”上有一个索引,集合包含500,000条记录。
如果我使用以下命令和管道:
db.collection.runCommand('aggregate', {pipeline: [ { "$match" : { "timestamp" : { "$gt" : 1388425361294 , "$lt" : 1388443361294}}}  ], explain: true})

执行计划基本符合预期,即扫描了4个文档。摘自“解释”:

"cursor" : {
        "cursor" : "BtreeCursor timestamp_1",
        "isMultiKey" : false,
        "n" : 4,
        "nscannedObjects" : 4,
        "nscanned" : 4,
        "nscannedObjectsAllPlans" : 4,
        "nscannedAllPlans" : 4,
        "scanAndOrder" : false,
        "indexOnly" : false,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "millis" : 0,
        "indexBounds" : {
                "timestamp" : [
                        [
                                1388425361294,
                                1388443361294
                        ]
                ]
        },
        .......

但是,一旦使用任何 $project 参数,行为就会发生 drastical 改变。以下命令(即使“country”字段可能不存在于任何文档中,也不会有任何区别):

db.collection.runCommand('aggregate', {pipeline: [ { "$project" : { "country" : "$country"} ,  { "$match" : { "timestamp" : { "$gt" : 1388425361294 , "$lt" : 1388443361294}}}  ], explain: true})

产生这个计划:

  "cursor" : {
          "cursor" : "BasicCursor",
          "isMultiKey" : false,
          "n" : 500001,
          "nscannedObjects" : 500001,
          "nscanned" : 500001,
          "nscannedObjectsAllPlans" : 50
          "nscannedAllPlans" : 500001,
          "scanAndOrder" : false,
          "indexOnly" : false,
          "nYields" : 0,
          "nChunkSkips" : 0,
          "millis" : 101,
          "indexBounds" : {

          },

很明显,这迫使扫描集合中的所有记录,这对我来说是不可接受的。

在使用 $project 管道阶段时,我是否漏掉了一些重要的东西?

3个回答

5
如果你首先使用$project,那么你正在进行一次集合扫描,输出该集合中具有该形式的所有文档,只包含country字段和_id。这与以下语句相同:"给我所有只包含country字段和_id的文档"。然后将此结果传递到下一个管道,它恰好是$match,导致全集合扫描。当然,在这里你将有两个完整的集合扫描,因为$match也不能再使用索引。你可能能够执行索引扫描而不是集合扫描,但正如你所说,唯一真正的方法是交换两者的顺序,以限制你的文档,然后进行投影。

1

看起来当前版本的MongoDB在可能的情况下会自动优化投影和匹配阶段的顺序。

如果一个字段在匹配阶段中被使用,并且是直接从原始文档中提取的(即通过前一个阶段添加或修改的字段除外),聚合管道将自动通过创建一个以该字段为第一阶段的新匹配阶段来进行优化,从而允许使用索引。

示例摘自有关聚合管道投影/匹配优化的文档

优化前的聚合:

{ 
  $addFields: 
  {
    maxTime: { $max: "$times" },
    minTime: { $min: "$times" }
  } 
},
{ 
  $project: 
  {
    _id: 1, name: 1, times: 1, maxTime: 1, minTime: 1,
    avgTime: { $avg: ["$maxTime", "$minTime"] }
  } 
},
{ 
  $match: 
  {
    name: "Joe Schmoe",
    maxTime: { $lt: 20 },
    minTime: { $gt: 5 },
    avgTime: { $gt: 7 }
  } 
}

优化后的聚合:
{ 
  $match: { name: "Joe Schmoe" } 
},
{ 
  $addFields: 
  {
    maxTime: { $max: "$times" },
    minTime: { $min: "$times" }
  } 
},
{ 
  $match: 
  { 
    maxTime: { $lt: 20 }, 
    minTime: { $gt: 5 } 
  } 
},
{ 
  $project: 
  {
    _id: 1, name: 1, times: 1, maxTime: 1, minTime: 1,
    avgTime: { $avg: ["$maxTime", "$minTime"] }
  } 
},
{ 
  $match: 
  { 
    avgTime: { $gt: 7 } 
  } 
}

0

在这种情况下,似乎应该反过来使用 $project 和 $match。


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