MongoDB 聚合管道是什么?常见 stage 有哪些?
简化版
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 }
])
这段逻辑表示:
- 过滤已支付订单;
- 按用户分组统计金额;
- 按总金额倒序;
- 取前 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 的数据量。