OPTIONS
翻译或纠错本页面

聚合管道的优化

聚合管道操作中有这样一个优化阶段,它会尝试改造管道以提升性能。

如要了解优化器如何转换一个给定的聚合管道,可以查看 db.collection.aggregate() 方法中的 说明 选项的输出结果。

具体的优化内容按不同版本的变化而定。

预测优化

聚合管道可以检测到是否仅使用文档中的一部分字段就可以完成聚合。如果是的话,管道就可以仅使用这些必要的字段,从而减少进入管道的数据量。

管道顺序的优化

$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 ,优化器允许你做很多优化。详情请查看 $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" } } }

优化器可以在 $redact 之前增加相同的 $match

{ $match: { year: 2014 } },
{ $redact: { $cond: { if: { $eq: [ "$level", 5 ] }, then: "$$PRUNE", else: "$$DESCEND" } } },
{ $match: { year: 2014, category: { $ne: "Z" } } }

管道合并优化

优化器可以在管道开始之前合并其他的管道。一般情况下,合并发生在所有顺序优化之后。

$sort + $limit 合并

如果 $sort$limit 前面,优化器可以把 $limit 合并在 $sort 内部。此时如果指定了限定返回 n 个结果,那么排序操作仅需要维护最前面的 n 个结果,MongoDB只需要在内存中存储 n 个元素 [1]。更多信息请查看 排序与内存

[1]

allowDiskUse 设置为 truen 数目超过 聚合的内存限制 时,优化依旧会进行。

$limit + $limit 合并

$limit 操作后面还有一个 $limit 操作,这两步可以合并成一个单独的 $limit 操作,此时限制的个数是前面两个限制个数中较小的值。例如,一个管道操作包含有如下操作序列:

{ $limit: 100 },
{ $limit: 10 }

此时,第二个 $limit 操作可以合并到第一个 $limit 操作中,最后生成一个 $limit 操作并且限制个数为初始两个限制个数 10010 中的较小的一个 10

{ $limit: 10 }

$skip + $skip 合并

$skip 操作后面还有一个 $skip 操作,这两步可以合并成一个单独的 $skip 操作,此时跳过的个数是前面两个跳过个数的和。例如,一个管道操作包含有如下操作序列:

{ $skip: 5 },
{ $skip: 2 }

此时,第二个 $skip 操作可以合并到第一个 $skip 操作中,最后生成一个 $skip 操作并且跳过个数为初始两个跳过个数 52 的相加值 7

{ $skip: 7 }

$match + $match 合并

$match 操作后面还有一个 $match 操作,可以将这两步中的条件使用 $and 表达式合并成一个单独的 $match 操作。例如,一个管道操作包含有如下操作序列:

{ $match: { year: 2014 } },
{ $match: { status: "A" } }

此时,第二个 $match 操作可以合并到第一个 $match 操作中,最后生成一个 $match 操作。

{ $match: { $and: [ { "year" : 2014 }, { "status" : "A" } ] } }

例子

下面的一些顺序优化的例子可以结合顺序重排和合并的优势。一般来说,合并优化在所有的顺序重排之后进行。

$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 前面,这时可以将这两个阶段合并来降低排序时需要的内存大小。详情请查看 $sort和$limit的合并

$limit + $skip + $limit + $skip 顺序

管道中如果交替包含了 $limit$skip :

{ $limit: 100 },
{ $skip: 5 },
{ $limit: 10 },
{ $skip: 2 }

The $skip + $limit 顺序优化 reverses the position of the { $skip: 5 } and { $limit: 10 } stages and increases the limit amount:

{ $limit: 100 },
{ $limit: 15},
{ $skip: 5 },
{ $skip: 2 }

优化器会把两个 $limit 合并为一个 $limit ,两个 $skip 合并为一个 $skip 。合并后的结果如下:

{ $limit: 15 },
{ $skip: 7 }

详情请查看 $limit和$limit的合并$skip和$skip的合并

参见

使用 db.collection.aggregate() 中的 explain 可以看到优化的更多细节。