聚集管道优化
聚集管道操作有一个优化阶段,试图重组管道以提高性能。
要查看优化器如何变换一个特定的聚集管道,在db.collection.aggregate()
方法中包括explain
选项。
优化操作因版本的不同而不同。
投影优化
聚集管道可以确定它是否只需要文档字段的一个子集来获取结果,如果是的化,管道只会使用这些必需的字段,减少的通过管道的数据量。
管道序列优化
$sort
+ $match
序列优化
当$sort
后面跟着$match
的时候,$match
会被移到$sort
前,以最小化排序的数据量。例如,如果管道由以下步骤组成:
{ $sort: { age : -1 } },
{ $match: { status: 'A' } }
在优化阶段,优化器把序列变成如下:
{ $match: { status: 'A' } },
{ $sort: { age : -1 } }
$skip
+$limit
序列优化
当$skip
后面有$limit
的时候,$limit
会被移到$skip
前面。重排之后,$limit
的值要增加$skip
的量。
例如,如果管道由以下步骤组成:
{ $skip: 10 },
{ $limit: 5 }
在优化阶段,优化器会把序列变成如下:
{ $limit: 15 },
{ $skip: 10 }
对于$sort
+$limit
的组合,优化操作允许有更多的机会,例如对于$sort+$skip+$limit
序列。
对于在分片集合上的聚集操作,该优化减少了从每个分片上返回的结果。
$redact+$match
序列优化
只要有可能,当管道的$redact
阶段后面直接跟着$match
阶段的时候,该聚集操作有时可以把$match
阶段的一部分添加到$redact
阶段之前。如果所添加的$match
阶段在管道的开始,该聚集操作可以使用索引来查询集合以限制进入管道的文档数量。更多信息请查看管道操作符与索引。
例如,如果管道由以下阶段组成:
{ $redact: { $cond: { if: { $eq: [ "$level", 5 ] }, then: "$$PRUNE", else: "$$DESCEND" } } },
{ $match: { year: 2014, category: { $ne: "Z" } } }
优化器可以把$match
阶段添加到$redact
阶段之前:
{ $match: { year: 2014 } },
{ $redact: { $cond: { if: { $eq: [ "$level", 5 ] }, then: "$$PRUNE", else: "$$DESCEND" } } },
{ $match: { year: 2014, category: { $ne: "Z" } } }
$project
+$skip
或$limit
序列优化
版本3.2新增。
当$project
后面跟着$skip
或者$limit
的时候,$skip
或$limit
可以移到$project
之前。例如,如果管道由以下阶段组成:
{ $sort: { age : -1 } },
{ $project: { status: 1, name: 1 } },
{ $limit: 5 }
在优化阶段,优化器把序列变成:
{ $sort: { age : -1 } },
{ $limit: 5 }
{ $project: { status: 1, name: 1 } },
管道合并优化
如果有可能,优化阶段会把一个管道阶段合并到它前面那一个去。一般地,合并操作出现在任何一个序列重组优化之后。
$sort+$limit
合并
当$sort
直接放在$limit
前面的时候,优化器可以把$limit
合并到$sort
里面。这使得排序操作只保留前n个结果,n是limit所指定的值,MongoDB只需要在内存中保留n条数据。查看$sort
操作符和内存获取更多信息。
$limit+$limit
合并
当一个$limit
后直接跟着另一个$limit
时,这两个阶段可以合并成一个$limit
,limit数量是两个初始limit数量的较小值。例如,一个管道包含如下序列:
{ $limit: 100 },
{ $limit: 10 }
第二个$limit
阶段可以合并到第一个$limit
阶段,结果只剩一个$limit
阶段,limit数量是10,是这两个limit中的较小值。
{ $limit: 10 }
$skip+$skip
合并
当一个$skip
后面直接跟着另一个$skip
时,这两个阶段可以合并成一个$skip
阶段,skip的数量是两个skip数量之和。例如,一个管道包含如下序列:
{ $skip: 5 },
{ $skip: 2 }
第二个$skip
可以合并至第一个$skip
,结果只有一个$skip
阶段,skip的数量是7,即这两个skip的数量之和:
{ $skip: 7 }
$match+$match
合并
当一个$match
后面直接跟着另一个$match
时,这两个阶段可以合并成一个$match
,用$and
操作符把它们的条件组合起来。例如,一个管道包含如下序列:
{ $match: { year: 2014 } },
{ $match: { status: "A" } }
第二个$match
可以合并至第一个$match
,结果只有一个$match
阶段:
{ $match: { $and: [ { "year" : 2014 }, { "status" : "A" } ] } }
$lookup+$unwind
合并
版本3.2新增。
当一个$unwind
直接跟着$lookup
时,并且$unwind
直接对$lookup
的as
字段操作,优化器就可以把$unwind
合并至$lookup
阶段内,这避免了创建大量的中间文档。
例如,一个管道包含如下序列:
{
$lookup: {
from: "otherCollection",
as: "resultingArray",
localField: "x",
foreignField: "y"
}
},
{ $unwind: "$resultingArray"}
优化器可以把$unwind
阶段合并至$lookup
阶段。如果你用explain
选项运行这个聚集,explain
输出如下的合并阶段:
{
$lookup: {
from: "otherCollection",
as: "resultingArray",
localField: "x",
foreignField: "y",
unwinding: { preserveNullAndEmptyArrays: false }
}
}
例子
以下例子是一些既可以用序列重排,又可以合并的序列。一般地,合并发生在序列重排之后。
$sort+$skip+$limit
序列
一个管道包含如下序列:$sort
后面跟着$skip
,然后跟着$limit
:
{ $sort: { age : -1 } },
{ $skip: 10 },
{ $limit: 5 }
首先,优化器执行$skip+$limit序列优化,把序列变成如下:
{ $sort: { age : -1 } },
{ $limit: 15 }
{ $skip: 10 }
重排时,$skip+$limit序列优化增加了$limit
的量。详情请看$skip+$limit序列优化。
重排之后,$sort
直接在$limit
之前了,管道就可以把这两个阶段合并,以减少排序操作过程中的内存占用。更多请查看$sor$limit合并。
$limit+$skip+$limit+$skip
序列
一个包含$limit
和$skip
阶段交替的序列的管道。
{ $limit: 100 },
{ $skip: 5 },
{ $limit: 10 },
{ $skip: 2 }
$skip+$limit
序列优化把{$skip: 5}
与{$limit: 10}
的位置交换以下,增加limit的量:
{ $limit: 100 },
{ $limit: 15},
{ $skip: 5 },
{ $skip: 2 }
优化器然后把这两个$limit
阶段合并成一个$limit
阶段,把两个$skip
阶段合并成一个$skip
阶段,结果序列如下:
{ $limit: 15 },
{ $skip: 7 }