必须用 aggregate() 而非 find() + 手动统计,因其在服务端高效执行分组、求和等操作;$group 的 _id 是表达式(如 "$status" 或 {a:"$a",b:"$b"}),需配合 $match 过滤空值、allowdiskuse:true 防内存溢出、$unwind 处理 $lookup 关联数组。

直接用 aggregate() 方法,别绕路写 find() + 手动遍历统计——后者在数据量稍大时就卡死或内存溢出。
聚合必须用 aggregate(),不是 find()
很多人误以为“查出来再 JS 算”更灵活,实际是把数据库当存储,放弃它的计算能力。MongoDB 的聚合管道在服务端完成分组、求和、去重等操作,网络只传最终结果。
-
find()返回游标或数组,适合取原始数据;aggregate()是专为变换、汇总设计的流水线 - 千万级数据下,
find()拉全量再 Node.js 里reduce(),极可能 OOM 或超时 -
aggregate()支持索引下推(如$match阶段能命中索引),而 JS 层过滤无法利用索引
$group 阶段的 _id 写法容易错
_id 在 $group 里不是字段名,而是分组依据表达式。写成字符串会报错或逻辑错误。
- 按单个字段分组:用
"$status"(带美元符),不是"status" - 按多个字段组合分组:用对象,如
{ status: "$status", category: "$category" } - 不需分组只做全局统计:设
_id: null,例如算总订单数:{ $group: { _id: null, total: { $sum: 1 } } } - 字段不存在时默认为
null,所以$group可能合并出意料之外的null组,建议先$match过滤掉空值
大数据量必须加 allowDiskUse: true
默认聚合内存限制 100 MB,一旦 $group 或 $sort 数据超出,会直接报错 errmsg: "Sort exceeded memory limit..."。
- 必须显式传
{ allowDiskUse: true }到aggregate()的第二个参数 - 仅限 MongoDB 4.4+,旧版本不支持该选项
- 磁盘落盘有 IO 开销,但比崩溃强;线上环境应配合索引优化,而非依赖
allowDiskUse - 示例:
collection.aggregate(pipeline, { allowDiskUse: true })
$lookup 关联后记得 $unwind 和 $project
用 $lookup 做左连接后,关联字段是数组(即使只匹配一条),直接 $group 会出错或重复计数。
- 必须跟
$unwind展开数组,例如:{ $unwind: "$userInfo" } - 否则
$group中引用"$userInfo.name"会返回undefined(因为它是数组) - 用
$project提前裁剪字段,减少后续阶段处理体积,尤其避免传整个用户文档 -
$lookup的localField和foreignField字段类型必须一致,字符串 ID 和 ObjectId 混用会导致匹配失败
真正卡住人的往往不是语法,而是没意识到 $group 的 _id 是表达式、没开 allowDiskUse、或者 $lookup 后忘了 $unwind —— 这三处不检查,聚合就永远跑不通。











