← 返回题目列表

MongoDB 聚合管道是什么?常见 stage 有哪些?

高频 中等 第 11 / 31 题 更新于 2026/07/28
MongoDBAggregation聚合管道数据分析

简化版

MongoDB 聚合管道是把文档按多个阶段依次处理的数据处理框架,类似流水线。常见 stage 有 $match 过滤、$project 投影、$group 分组、$sort 排序、$limit 限制数量、$lookup 关联、$unwind 展开数组。性能上通常要尽量让 $match$project 靠前,减少后续处理的数据量。

详细版

示例:

db.orders.aggregate([
  { $match: { status: "PAID" } },
  { $group: { _id: "$userId", total: { $sum: "$amount" } } },
  { $sort: { total: -1 } },
  { $limit: 10 }
])

这段逻辑表示:

  1. 过滤已支付订单;
  2. 按用户分组统计金额;
  3. 按总金额倒序;
  4. 取前 10 名。

聚合管道适合统计、报表、数据转换、数组处理、跨集合补充数据。但复杂聚合要关注索引、内存、排序、分组数据量和 $lookup 成本。

完整版教学

一、聚合管道像数据流水线

普通 find 更像简单查询,聚合管道更像一组数据处理步骤。每个 stage 接收上一阶段输出的文档,再产生新的文档流。

例如:

订单集合 -> 过滤 -> 分组 -> 排序 -> 输出

这种模型清晰,适合表达复杂统计。

二、$match 要尽量靠前

$match 用于过滤文档:

{ $match: { status: "PAID" } }

如果放在管道前面,可以尽早减少文档数量,也更容易利用索引。把大量无关数据带到后面再过滤,会浪费内存和 CPU。

当然,如果前面 stage 改变了字段结构,某些 $match 必须放在后面,这要结合语义判断。

三、$project 控制输出字段

$project 可以选择字段、重命名字段、计算新字段:

{
  $project: {
    userId: 1,
    amount: 1,
    month: { $month: "$createdAt" }
  }
}

它的价值是减少后续阶段需要携带的数据,也能把文档转换成更适合统计的形状。

四、$group 是分组统计核心

$group 类似 SQL 的 GROUP BY

{
  $group: {
    _id: "$userId",
    total: { $sum: "$amount" },
    count: { $sum: 1 }
  }
}

常见累加器包括 $sum$avg$min$max$push 等。

分组可能消耗大量内存。分组前要尽量过滤数据,必要时考虑预聚合、离线统计或分析系统。

五、$unwind 用于展开数组

如果文档里有数组:

{ title: "A", tags: ["java", "db"] }

$unwind: "$tags" 会把一条文档展开成多条,每条对应一个标签。

这适合统计标签、明细项、嵌套数组。但数组很大时会导致文档数量膨胀,要特别注意。

六、$lookup 可以关联但不能滥用

$lookup 可以做集合关联,类似左外连接:

{
  $lookup: {
    from: "users",
    localField: "userId",
    foreignField: "_id",
    as: "user"
  }
}

它适合补充少量关联信息,但如果业务大量依赖复杂 JOIN,可能说明文档模型设计不适合这个查询。高频展示字段可以考虑冗余或预聚合。

七、常见误区与追问

stage主要作用面试里要补充的风险
$match过滤文档尽量靠前并配合索引
$group分组聚合数据量大时内存压力高
$sort排序未被索引支持时可能成为瓶颈
$lookup集合关联高频复杂 JOIN 可能说明模型不合适

记忆钩子:聚合管道优化的主线是“越早减少文档,越少进入重 stage”。先过滤、再整形、再统计,最后排序和关联。

如果 100 万条订单中只有 2 万条是 PAID,先 $match$group,后续只处理约 2% 数据;如果先 $group 再过滤,就可能让无关的 98 万条也参与分组,内存和 CPU 都会被浪费。

  • 误区:聚合管道就是 SQL JOIN 的替代品。 聚合能做很多转换和关联,但 MongoDB 建模仍应减少高频复杂跨集合 JOIN。
  • 误区:所有 $match 都能随便移动到最前面。 如果前面的 $project$unwind 改变了字段结构,某些过滤只能放在语义正确的位置。
  • 误区:$lookup 能用就说明设计没问题。 高频页面大量依赖 $lookup,可能更应该冗余展示字段或调整文档边界。
  • 追问:为什么 $group 前要尽量过滤? $group 会按 key 聚合中间状态,输入文档越多,内存、排序和溢写压力越大。
  • 追问:$unwind 有什么风险? 数组展开会放大文档数量,1 万篇文章每篇 20 个标签,展开后就是 20 万条中间文档。
  • 追问:聚合性能不好先看什么? 先看前置过滤是否用索引、是否有大规模 $sort/$group$lookup 关联字段是否有索引,以及中间数据是否过大。

八、加强记忆

MongoDB 聚合管道记成“文档流水线”:$match 先过滤,$project 整形,$group 统计,$sort 排序,$lookup 补关联,$unwind 展开数组。优化重点是减少进入后续 stage 的数据量。