首页/数据/NoSQL 速查/聚合管道总览
MongoDB99 条

聚合管道总览

aggregate 把文档流依次通过各阶段处理:过滤、分组、投影、排序、联表、写出;每个阶段输出交给下一阶段。

NoSQL 速查数据更新于 2026-10-13

聚合管道是什么

聚合管道是 MongoDB 的通用计算框架:db.collection.aggregate([...]) 把文档流依次送进各阶段,每个阶段的输出就是下一阶段的输入。阶段按用途分四类——过滤($match)、变换($project/$addFields)、重组($group/$unwind/$sort)、外部数据($lookup/$unionWith/$out)。它是做统计、报表与 ETL 的主力工具,$match 的条件写法与 find 完全通用。

基本信息

语法db.collection.aggregate([ stage, ... ], options)
常用阶段$match / $group / $project / $sort / $limit / $skip / $unwind / $lookup
常用选项allowDiskUse / maxTimeMS / collation / explain / hint
内存上限单阶段输出上限 100MB,超限需 allowDiskUse: true
返回默认返回游标,也可用 $out / $merge 落库

典型使用场景

  • 多维统计报表:按日、按渠道、按状态分组汇总
  • 明细展开再聚合:$unwind 拆数组后按元素统计
  • 跨集合补数据:$lookup 关联用户或商品维表
  • 数据搬迁与清洗:$project 改结构后 $merge 写回新集合
  • 口径核对:与 find 结果交叉验证,确认过滤条件正确
  • 性能诊断:用 explain 观察 $match 是否被下推到索引扫描

端到端示例

端到端示例
db.orders.aggregate([
  { $match: { status: "paid", ts: { $gte: ISODate("2026-01-01T00:00:00Z") } } },  # 先过滤,减少后续数据量
  { $group: { _id: "$sku", totalQty: { $sum: "$qty" }, orders: { $sum: 1 } } },
  { $sort: { totalQty: -1 } },                                                      # 按销量降序
  { $limit: 5 }                                                                     # 只看前 5 组
])

阶段速查

阶段作用注意点
$match按条件筛选,尽量放最前写法同 find,可走索引
$group分组并计算累加器大分组需 allowDiskUse
$project挑字段、算新字段、改名包含与排除不能混写
$unwind把数组拆成多条文档空数组会丢文档,可保留
$lookup左外连接另一集合from 侧连接字段要有索引
$merge / $out把结果写回集合$out 覆盖整表,$merge 可增量
$match 与 $sort 放在管道最前面才能借上索引;一旦被 $project 或 $unwind 改变了文档结构,后续阶段就只能走内存处理,成本立刻上升。
用 aggregate 的 { explain: true } 选项或 .explain("executionStats") 观察执行计划,重点确认 $match 是否被优化器下推到 IXSCAN 而不是 COLLSCAN。
管道默认每个阶段最多用 100MB 内存,超出会直接报错;allowDiskUse: true 会落临时文件,能跑通但磁盘 IO 会明显上升,属于临时手段。
长管道难排错时用中间落库法:把前半段结果写进临时集合,再对临时集合写后半段,这样每一步的输入都能单独核对。

性能与排错要点

  • 先用 $match 缩小范围,再进入 $group 与 $sort,是全链路最省资源的顺序
  • $group 的 _id 分组键尽量用低基数、语义清晰的字段,避免用整个文档当键
  • $lookup 之后紧跟 $match 时,把条件写进 $lookup 的 pipeline 形式可减少返回的匹配文档
  • $facet 可以在一趟扫描里产出多个维度的结果,适合报表类一次出多组统计
  • $sort 之前先 $limit 只是取前 N,真正需要全局排序时排序键要有索引
  • 用 maxTimeMS 给线上临时查询设超时,避免长管道把资源占满

关联后落库

关联后落库
db.orders.aggregate([
  { $match: { status: "paid" } },
  { $lookup: { from: "products", localField: "sku", foreignField: "sku", as: "p" } },
  { $unwind: "$p" },                                    # 拍平单元素数组
  { $project: { _id: 0, sku: 1, name: "$p.name", totalQty: 1 } },
  { $merge: { into: "report_sku", whenMatched: "replace", whenNotMatched: "insert" } }
])
$facet 允许在一趟数据扫描中并行跑多条子管道,做多维度报表时比拆成多次查询更省 IO,但每个子管道的中间结果仍受 100MB 内存限制约束。

常用累加器与表达式

  • $sum 求和或计数,$avg / $min / $max 取组内统计量
  • $push 收集成数组,$addToSet 去重收集
  • $first / $last 取每组首尾值,只有排序后语义才确定
  • $count 是较新版本提供的计数累加器,等价于 $sum 常量 1
  • $cond / $switch / $ifNull 做条件分支与空值兜底
  • $dateToString / $dateTrunc 处理按日按月的分桶聚合
$sort 在管道内排序时的内存上限同样是 100MB,且排序键若产生于 $group 之后就无法借索引,只能落内存或磁盘,报表设计要尽量让排序键来自可预先聚合的字段。
$out 与 $merge 必须是管道的最后一个阶段,写在中间会被直接拒绝;调试阶段的中间结果先用 $limit 抽查,确认口径再放开全量。

易错点

  • 阶段顺序写反(先 $group 再 $match)会让过滤发生在全量分组之后,数据量与耗时可差一个数量级
  • $project 里把字段包含与排除混写会直接报错,只有 _id 可以例外
  • $lookup 的 from 侧连接字段没有索引时是逐文档扫描,整个管道吞吐会明显下降
  • $out 会覆盖目标集合,试运行阶段先写临时集合核对结果,确认无误再切换正式表
  • $unwind 之后文档数会成倍放大,紧跟着的 $group 内存占用要按展开后的规模估算,否则容易触发内存上限

数据来源与参考

  • MongoDB Manual: Aggregation Pipeline
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌