我使用聚合框架来分组一些数据。据观察,当使用$ 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参数,行为就会彻底改变。以下命令('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管道阶段的重要内容?
答案 0 :(得分:3)
如果您$project
首先进行集合扫描,则使用该表单输出该集合中的所有文档。这跟说:
“仅使用country
字段和_id
”向我提供集合中的所有文档
然后将此结果传递到恰好是$match
的下一个管道,该管道将导致完整的集合扫描。当然,您将在此处进行两次完整的集合扫描,因为$match
也不能再使用索引。
您可能能够执行索引扫描而不是集合,但正如您所说,唯一真正的方法是实际切换两者的顺序,以便限制文档然后进行投影。
答案 1 :(得分:1)
似乎当前版本的 MongoDB 会在可能的情况下自动优化投影和匹配阶段的顺序。
如果在匹配阶段使用了一个字段,并且是从原始文档中直接提取(即投影)的(不是由前一个阶段添加或修改的),则聚合管道将通过创建一个新的匹配阶段来自动优化将此字段作为第一阶段,从而允许使用索引。
从 documentation about projection/match optimization of aggregation pipeline 中提取的示例:
优化前的聚合:
{
$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 }
}
}
答案 2 :(得分:0)
在这种情况下,似乎应该以相反的顺序使用$ project和$ match。