聚合管道优化

    在本页面

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

    要查看优化程序如何转换特定聚合管道,请在db.collection.aggregate()方法中包含explain选项。

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

    投影优化

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

    管道序列优化

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

    对于包含投影阶段($project$unset$addFields$set)后跟$match阶段的聚合管道,MongoDB 将$match阶段中不需要在投影阶段计算的值的任何过滤器移动到投影前的新$match阶段。

    如果聚合管道包含多个投影 and/or $match阶段,MongoDB 会为每个$match阶段执行此优化,将每个$match过滤器移动到过滤器不依赖的所有投影阶段之前。

    考虑以下阶段的管道:

    1. { $addFields: {
    2. maxTime: { $max: "$times" },
    3. minTime: { $min: "$times" }
    4. } },
    5. { $project: {
    6. _id: 1, name: 1, times: 1, maxTime: 1, minTime: 1,
    7. avgTime: { $avg: ["$maxTime", "$minTime"] }
    8. } },
    9. { $match: {
    10. name: "Joe Schmoe",
    11. maxTime: { $lt: 20 },
    12. minTime: { $gt: 5 },
    13. avgTime: { $gt: 7 }
    14. } }

    优化器将$match阶段分成四个单独的过滤器,一个用于$match查询文档中的每个键。然后优化器将每个筛选器移动到尽可能多的投影阶段之前,根据需要创建新的$match阶段。鉴于此示例,优化程序生成以下优化管道:

    1. { $match: { name: "Joe Schmoe" } },
    2. { $addFields: {
    3. maxTime: { $max: "$times" },
    4. minTime: { $min: "$times" }
    5. } },
    6. { $match: { maxTime: { $lt: 20 }, minTime: { $gt: 5 } } },
    7. { $project: {
    8. _id: 1, name: 1, times: 1, maxTime: 1, minTime: 1,
    9. avgTime: { $avg: ["$maxTime", "$minTime"] }
    10. } },
    11. { $match: { avgTime: { $gt: 7 } } }

    $match过滤器{ avgTime: { $gt: 7 } }取决于$project阶段来计算avgTime字段。 $project阶段是此管道中的最后一个投影阶段,因此avgTime上的$match过滤器无法移动。

    maxTimeminTime字段在$addFields阶段计算,但不依赖于$project阶段。优化器为这些字段上的过滤器创建了一个新的$match阶段,并将其放在$project阶段之前。

    $match过滤器{ name: "Joe Schmoe" }不使用在$project$addFields阶段计算的任何值,因此它在两个投影阶段之前被移动到新的$match阶段。

    [success] 注意

    优化后,过滤器{ name: "Joe Schmoe" }位于管道开头的$match阶段。这具有额外的好处,即允许聚合在最初查询集合时在name字段上使用索引。有关更多信息,请参见管道操作符和索引

    $sort + $match 序列优化

    如果序列中带有$sort后跟$match,则$match会移动到$sort之前,以最大程度的减少要排序的对象的数量。例如,如果管道包含以下阶段:

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

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

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

    $redact + $match 序列优化

    如果可能,当管道的$redact阶段紧在$match阶段之后时,聚合有时可以在$redact阶段之前添加$match阶段的一部分。如果添加的$match阶段位于管道的开头,则聚合可以使用索引以及查询集合来限制进入管道的文档数。有关更多信息,请参见管道操作符和索引。 例如,如果管道包含以下阶段:

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

    优化器可以在$redact阶段之前添加相同的$match阶段:

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

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

    3.2版本中的新功能。

    当有一个$project$unset之后跟有$skip序列时,$skip 会移至$project之前。例如,如果管道包括以下阶段:

    1. { $sort: { age : -1 } },
    2. { $project: { status: 1, name: 1 } },
    3. { $skip: 5 }

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

    1. { $sort: { age : -1 } },
    2. { $skip: 5 },
    3. { $project: { status: 1, name: 1 } }

    管道聚合优化

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

    $sort + $limit合并

    Mongodb 4.0版本的改变。

    当一个$sort先于$limit,优化器可以聚结$limit$sort,如果没有中间阶段的修改文件(例如,使用数$unwind$group)。如果有管道阶段会更改和阶段之间的文档数,则MongoDB将不会合并$limit到 。$sort$sort$limit

    例如,如果管道包括以下阶段:

    1. { $sort : { age : -1 } },
    2. { $project : { age : 1, status : 1, name : 1 } },
    3. { $limit: 5 }

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

    1. {
    2. "$sort" : {
    3. "sortKey" : {
    4. "age" : -1
    5. },
    6. "limit" : NumberLong(5)
    7. }
    8. },
    9. { "$project" : {
    10. "age" : 1,
    11. "status" : 1,
    12. "name" : 1
    13. }
    14. }

    这样,排序操作就可以仅在执行过程中保持最高n结果,这n是指定的限制,MongoDB仅需要将n项目存储在内存中 [1]。有关更多信息,请参见$ sort运算符和内存

    $skip进行序列优化

    如果$skip$sort$limit阶段之间有一个阶段,MongoDB将合并 $limit到该$sort阶段并增加该 $limit$skip。有关示例,请参见 $ sort + $ skip + $ limit序列

    [1]当优化仍将适用 allowDiskUsetruen项目超过 聚集内存限制

    $limit+ $limit合并

    $limit紧接着另一个时 $limit,两个阶段可以合并为一个阶段 $limit,其中限制量为两个初始限制量中的较小者。例如,管道包含以下序列:

    1. { $limit: 100 },
    2. { $limit: 10 }

    然后,第二$limit级可以聚结到第一 $limit阶段,并导致在单个$limit 阶段,即限制量10是两个初始极限的最小10010

    1. { $limit: 10 }

    $skip+ $skip合并

    $skip紧跟另一个$skip,这两个阶段可合并成一个单一的$skip,其中跳过量为总和的两个初始跳过量。例如,管道包含以下序列:

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

    然后,第二$skip阶段可以合并到第一 $skip阶段,并导致单个$skip 阶段,其中跳过量7是两个初始限制5和的总和2

    1. { $skip: 7 }

    $match+ $match合并

    当一个$match紧随另一个紧随其后时 $match,这两个阶段可以合并为一个单独 $match的条件 $and。例如,管道包含以下序列:

    1. { $match: { year: 2014 } },
    2. { $match: { status: "A" } }

    然后,第二$match阶段可以合并到第一 $match阶段,从而形成一个$match 阶段

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

    $lookup + $unwind 合并

    3.2版中的新功能。

    当a $unwind立即紧随其后 $lookup,并且在 领域$unwind运行时,优化程序可以将其合并 到阶段中。这样可以避免创建较大的中间文档。as$lookup$unwind$lookup

    例如,管道包含以下序列:

    1. {
    2. $lookup: {
    3. from: "otherCollection",
    4. as: "resultingArray",
    5. localField: "x",
    6. foreignField: "y"
    7. }
    8. },
    9. { $unwind: "$resultingArray"}

    优化器可以将$unwind阶段合并为 $lookup阶段。如果使用explain 选项运行聚合,则explain输出将显示合并阶段:

    1. {
    2. $lookup: {
    3. from: "otherCollection",
    4. as: "resultingArray",
    5. localField: "x",
    6. foreignField: "y",
    7. unwinding: { preserveNullAndEmptyArrays: false }
    8. }
    9. }

    例子

    $limit $skip $limit $skip 序列 止于Mongodb4.0

    管道包含一系列交替的$limit$skip阶段:

    1. { $limit: 100 },
    2. { $skip: 5 },
    3. { $limit: 10 },
    4. { $skip: 2 }

    $skip $limit 序列优化反转{ $skip: 5 }{ $limit: 10 }阶段的位置并增加限制量:

    1. { $limit: 100 },
    2. { $limit: 15},
    3. { $skip: 5 },
    4. { $skip: 2 }

    然后,优化器将两个$limit阶段合并为一个$limit阶段,将两个$skip阶段合并为一个$skip阶段。结果序列如下:

    1. { $limit: 15 },
    2. { $skip: 7 }

    有关详细信息,请参阅$limit $limit 合并$skip $skip 合并

    [success] 可以看看

    db.collection.aggregate()中的说明选项

    译者:李冠飞

    校对:李冠飞

    参见

    原文 - Aggregation Pipeline Optimization