聚合管道概述
本教程共 50 篇 · 第 28 篇 · 更新于 2026-07-30 · 约 7 分钟阅读
28. 聚合管道概述
本节目标:理解聚合管道(Aggregation Pipeline)是怎么回事,会用 aggregate() 把多个阶段串成流水线,并知道 $match 要尽早写来减少数据量。
find 擅长「按条件取文档、挑字段」,但遇到「按用户分组算总额」「统计各类商品销量」这种要多步加工的分析,find 就力不从心了。聚合框架(Aggregation Framework)就是干分析的利器。
db.orders.insertMany([
{ _id: 1001, user_id: 1, status: "completed", total: 657, created_at: ISODate("2024-06-01") },
{ _id: 1002, user_id: 2, status: "completed", total: 1099, created_at: ISODate("2024-06-05") },
{ _id: 1003, user_id: 1, status: "shipped", total: 597, created_at: ISODate("2024-06-10") },
{ _id: 1004, user_id: 3, status: "pending", total: 528, created_at: ISODate("2024-06-12") },
{ _id: 1005, user_id: 2, status: "cancelled", total: 398, created_at: ISODate("2024-06-15") }
])
什么是管道(Pipeline)
聚合像工厂流水线:文档从一头进去,经过一道道工序(阶段 / Stage),每道工序加工后传给下一道,最后从另一头出来的是加工好的结果。
aggregate() 接收一个阶段数组,每个阶段是一个以运算符命名的文档:
// 先过滤已完成订单,再按用户分组算总额
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $group: { _id: "$user_id", totalAmount: { $sum: "$total" } } }
])
阶段(Stage)是流水线的一环
每个阶段只做一件事:$match 负责筛、$group 负责聚、$sort 负责排。阶段按数组顺序依次执行,前一阶段的输出是后一阶段的输入。
Note阶段名都以
$开头,比如$match、$group、$project、$sort、$limit。后面几章会逐个拆开讲。
第一个阶段放 $match:尽早缩小数据
这是我反复强调的经验:能过滤的,越早过滤越好。把 $match 写在流水线最前面,能让后续阶段处理更少的文档,速度明显更快。
// 好写法:先 $match 缩小范围
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $group: { _id: "$user_id", cnt: { $sum: 1 } } }
])
如果先把 $group 放前面、最后才 $match,相当于先对全部订单分组再筛,白白多算。
Warning
$match放在最前还能利用索引;放中间或最后,往往已经用不上索引了。性能差别在大数据量时非常明显。
聚合与 find 的分工
| 能力 | find | 聚合管道 |
|---|---|---|
| 条件筛选 | 强 | $match 同样强 |
| 字段投影 | 支持 | $project 更强 |
| 排序/分页 | 支持 | $sort/$limit 支持 |
| 分组统计 | 不支持 | $group 核心能力 |
| 多集合关联 | 不支持 | $lookup 支持 |
| 字段重塑/计算 | 弱 | 表达式丰富 |
一句话:简单取数用 find;要分组、关联、复杂计算,上聚合。
聚合默认不改原数据
Tip普通的
aggregate()只是读取并加工,不会改动集合里的任何文档。只有管道里显式出现$out或$merge阶段,才会把结果写回集合。
下一章我们细看最常用的一对阶段:$match 过滤,以及 $project 重塑字段。