聚集管道优化

聚集管道操作有一个优化阶段,试图重组管道以提高性能。

要查看优化器如何变换一个特定的聚集管道,在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直接对$lookupas字段操作,优化器就可以把$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 }