聚合管道是统计分析平台的核心数据引擎,需严格遵循$match前置、$group精简分组键、$lookup加pipeline过滤、导出启用allowdiskuse等优化原则。

聚合管道本身不是平台,而是构建统计分析平台的核心数据引擎——用得好,它能扛住千万级订单的实时聚合;用得糙,一个 $group 就让查询卡死。
$match 必须放在最前面,否则索引基本失效
很多团队在写聚合时习惯先 $unwind 或 $group,再加 $match 过滤结果。这会导致全量文档进入后续阶段,MongoDB 无法利用 status、created_at 等字段上的索引。
- 正确顺序:第一个阶段必须是
$match,且条件字段应有对应索引(如{ status: 1, created_at: 1 }) - 复合过滤要写成单个
$match,不要拆成多个阶段——MongoDB 不会自动合并 - 时间范围务必用
ISODate(),避免字符串比较("2024-01-01"会被当作文本处理)
$group 阶段前必须明确分组键与聚合表达式
$group 是统计逻辑的落地点,但也是性能黑洞。它默认在内存中完成,若分组后文档数暴涨或字段类型混乱(比如把 price 当字符串累加),会直接触发 Exceeded memory limit 错误。
- 分组键尽量精简:
_id: "$user_id"比_id: { id: "$user_id", name: "$name" }更快,也更易命中索引 - 聚合字段用明确类型操作符:
$sum: "$amount"要求amount是数字;若含空值或字符串,先用$cond+$toDouble清洗 - 避免在
$group中做复杂计算(如嵌套$map),移到$project阶段更可控
$lookup 关联大表必须加 pipeline 过滤
电商场景中常见「订单 → 商品 → 类别」三级关联。直接 $lookup 整张 products 表,哪怕只查 10 条订单,也会拉取全部商品文档,再逐条匹配。
- 必须用
pipeline参数在远程集合上提前过滤:pipeline: [ { $match: { _id: { $in: "$items.product_id" } } } ] - 关联后立刻
$unwind,并检查是否可能产生空数组(加preserveNullAndEmptyArrays: false) - 如果只是取类别名,不要
$lookup整个商品文档,改用$lookup+$project只保留category字段
聚合结果导出或分页需警惕内存与游标限制
聚合结果超过 100MB 或 10 万文档时,db.collection.aggregate() 默认会失败。这不是配置问题,而是 WiredTiger 引擎的硬限制。
- 导出大量数据:加
{ allowDiskUse: true },但要注意磁盘 I/O 成为瓶颈 - 前端分页展示:别用
$skip+$limit做深分页,改用“上次最后 ID”方式(如{ _id: { $gt: ObjectId("...") } }) - 实时看板类需求:考虑用
$facet一次返回多维度统计,减少请求数,但注意总内存消耗
真正难的从来不是写出能跑的聚合语句,而是判断哪一步该建索引、哪个字段该清洗、什么时候该拆成两个聚合调用——这些决策没有银弹,全靠对数据分布和业务语义的理解。











