# 聚合管道优化

在本页面

* [投影优化](#projection-optimization)
* [管道序列优化](#pipeline-sequence-optimization)
* [管道聚结优化](#pipeline-coalescence-optimization)
* [例子](#example)

聚合管道操作具有优化阶段，该阶段试图重塑管道以改善性能。

要查看优化程序如何转换特定聚合管道，请在[db.collection.aggregate()](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)方法中包含[explain](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)选项。

优化可能会在不同版本之间发生变化。

## 投影优化

聚合管道可以确定它是否仅需要文档中的字段的子集来获得结果。如果是这样，管道将只使用那些必需的字段，减少通过管道的数据量。

## 管道序列优化

### ($project or $unset or $addFields or $set) + $match 序列优化

对于包含投影阶段([$project](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)或[$unset](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)或[$addFields](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)或[$set](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization))后跟[$match](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段的聚合管道，MongoDB 将[$match](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段中不需要在投影阶段计算的值的任何过滤器移动到投影前的新[$match](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段。

如果聚合管道包含多个投影 and/or [$match](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段，MongoDB 会为每个[$match](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段执行此优化，将每个[$match](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)过滤器移动到过滤器不依赖的所有投影阶段之前。

考虑以下阶段的管道：

```
{ $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](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段分成四个单独的过滤器，一个用于[$match](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)查询文档中的每个键。然后优化器将每个筛选器移动到尽可能多的投影阶段之前，根据需要创建新的[$match](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段。鉴于此示例，优化程序生成以下优化管道：

```
{ $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 } } }
```

[$match](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)过滤器`{ avgTime: { $gt: 7 } }`取决于[$project](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段来计算`avgTime`字段。 [$project](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段是此管道中的最后一个投影阶段，因此`avgTime`上的[$match](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)过滤器无法移动。

`maxTime`和`minTime`字段在[$addFields](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段计算，但不依赖于[$project](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段。优化器为这些字段上的过滤器创建了一个新的[$match](https://github.com/mongodb-china/MongoDB-CN-Manual/tree/8490376c81d56eff95abbaddc6ee414b1e1c9705/docs/Aggregation/Aggregation-Pipeline/reference-operator-aggregation-match.html#pipe._S_match)阶段，并将其放在[$project](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段之前。

[$match](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)过滤器`{ name: "Joe Schmoe" }`不使用在[$project](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)或[$addFields](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段计算的任何值，因此它在两个投影阶段之前被移动到新的[$match](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段。

> **\[success] 注意**
>
> 优化后，过滤器`{ name: "Joe Schmoe" }`位于管道开头的[$match](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段。这具有额外的好处，即允许聚合在最初查询集合时在`name`字段上使用索引。有关更多信息，请参见[管道操作符和索引](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)。

### $sort + $match 序列优化

如果序列中带有[$sort](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)后跟[$match](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)，则[$match](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)会移动到[$sort](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)之前，以最大程度的减少要排序的对象的数量。例如，如果管道包含以下阶段：

```
{ $sort: { age : -1 } }, 
{ $match: { status: 'A' } }
```

在优化阶段，优化程序将序列转换为以下内容：

```
{ $match: { status: 'A' } }, 
{ $sort: { age : -1 } }
```

### $redact + $match 序列优化

如果可能，当管道的[$redact](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段紧在[$match](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段之后时，聚合有时可以在[$redact](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段之前添加[$match](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段的一部分。如果添加的[$match](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段位于管道的开头，则聚合可以使用索引以及查询集合来限制进入管道的文档数。有关更多信息，请参见[管道操作符和索引](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)。 例如，如果管道包含以下阶段：

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

优化器可以在[$redact](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段之前添加相同的[$match](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段：

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

### `$project`/ `$unset` + `$skip`序列优化

*3.2版本中的新功能。*

当有一个[`$project`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)或[`$unset`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)之后跟有[`$skip`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)序列时，[`$skip`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization) 会移至[`$project`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)之前。例如，如果管道包括以下阶段：

```
{ $sort: { age : -1 } },
{ $project: { status: 1, name: 1 } },
{ $skip: 5 }
```

在优化阶段，优化器将序列转换为以下内容：

```
{ $sort: { age : -1 } },
{ $skip: 5 },
{ $project: { status: 1, name: 1 } }
```

## 管道聚合优化

如果可能，优化阶段将一个管道阶段合并到其前身。通常，合并发生在任何序列重新排序优化之后。

### `$sort` + `$limit`合并

*Mongodb 4.0版本的改变。*

当一个[`$sort`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)先于[`$limit`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)，优化器可以聚结[`$limit`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)到[`$sort`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)，如果没有中间阶段的修改文件（例如，使用数[`$unwind`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)，[`$group`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)）。如果有管道阶段会更改和阶段之间的文档数，则MongoDB将不会合并[`$limit`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)到 。[`$sort`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)[`$sort`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)[`$limit`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)

例如，如果管道包括以下阶段：

```
{ $sort : { age : -1 } },
{ $project : { age : 1, status : 1, name : 1 } },
{ $limit: 5 }
```

在优化阶段，优化器将序列合并为以下内容：

```
{
    "$sort" : {
       "sortKey" : {
          "age" : -1
       },
       "limit" : NumberLong(5)
    }
},
{ "$project" : {
         "age" : 1,
         "status" : 1,
         "name" : 1
  }
}
```

这样，排序操作就可以仅在执行过程中保持最高`n`结果，这`n`是指定的限制，MongoDB仅需要将`n`项目存储在内存中 [\[1\]](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)。有关更多信息，请参见[$ sort运算符和内存](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)。

> 用`$skip`进行序列优化
>
> 如果[`$skip`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)在[`$sort`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization) 和[`$limit`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段之间有一个阶段，MongoDB将合并 [`$limit`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)到该[`$sort`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段并增加该 [`$limit`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)值[`$skip`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)。有关示例，请参见 [$ sort + $ skip + $ limit序列](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)。
>
> [\[1\]](https://docs.mongodb.com/manual/core/aggregation-pipeline-optimization/#id1)当优化仍将适用 `allowDiskUse`是`true`与`n`项目超过 [聚集内存限制](https://docs.mongodb.com/manual/core/aggregation-pipeline-limits/#agg-memory-restrictions)。

### `$limit`+ `$limit`合并

当[`$limit`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)紧接着另一个时 [`$limit`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)，两个阶段可以合并为一个阶段 [`$limit`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)，其中限制量为两个初始限制量中的较小者。例如，管道包含以下序列：

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

然后，第二[`$limit`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)级可以聚结到第一 [`$limit`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段，并导致在单个[`$limit`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization) 阶段，即限制量`10`是两个初始极限的最小`100`和`10`。

```
{ $limit: 10 }
```

### `$skip`+ `$skip`合并

当[`$skip`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)紧跟另一个[`$skip`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)，这两个阶段可合并成一个单一的[`$skip`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)，其中跳过量为总和的两个初始跳过量。例如，管道包含以下序列：

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

然后，第二[`$skip`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段可以合并到第一 [`$skip`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段，并导致单个[`$skip`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization) 阶段，其中跳过量`7`是两个初始限制`5`和的总和`2`。

```
{ $skip: 7 }
```

### `$match`+ `$match`合并

当一个[`$match`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)紧随另一个紧随其后时 [`$match`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)，这两个阶段可以合并为一个单独 [`$match`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)的条件 [`$and`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)。例如，管道包含以下序列：

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

然后，第二[`$match`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段可以合并到第一 [`$match`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段，从而形成一个[`$match`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization) 阶段

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

### `$lookup` + `$unwind` 合并

*3.2版中的新功能。*

当a [`$unwind`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)立即紧随其后 [`$lookup`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)，并且在 领域[`$unwind`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)运行时，优化程序可以将其合并 到阶段中。这样可以避免创建较大的中间文档。`as`[`$lookup`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)[`$unwind`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)[`$lookup`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)

例如，管道包含以下序列：

```
{
  $lookup: {
    from: "otherCollection",
    as: "resultingArray",
    localField: "x",
    foreignField: "y"
  }
},
{ $unwind: "$resultingArray"}
```

优化器可以将[`$unwind`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段合并为 [`$lookup`](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段。如果使用`explain` 选项运行聚合，则`explain`输出将显示合并阶段：

```
{
  $lookup: {
    from: "otherCollection",
    as: "resultingArray",
    localField: "x",
    foreignField: "y",
    unwinding: { preserveNullAndEmptyArrays: false }
  }
}
```

## 例子

### $limit $skip $limit $skip 序列

止于Mongodb4.0

管道包含一系列交替的[$limit](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)和[$skip](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段：

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

[$skip $limit 序列优化](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)反转`{ $skip: 5 }`和`{ $limit: 10 }`阶段的位置并增加限制量：

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

然后，优化器将两个[$limit](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段合并为一个[$limit](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段，将两个[$skip](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段合并为一个[$skip](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)阶段。结果序列如下：

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

有关详细信息，请参阅[$limit $limit 合并](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)和[$skip $skip 合并](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)。

> **\[success] 可以看看**
>
> [db.collection.aggregate()](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)中的[说明](https://docs.mongoing.com/aggregation/aggregation-pipeline/aggregation-pipeline-optimization)选项

译者：李冠飞

校对：李冠飞
