调试聚合管道应分阶段快照:用$facet插入中间检查各段输出,临时替换$group为$limit+$addFields显式打印字段类型与长度,结合explain查看真实执行路径,用toArray()捕获完整错误堆栈。

用 $facet 拆解并观察每个阶段的输出
聚合管道出错时,错误常被吞掉或只报“invalid pipeline operator”,根本看不出哪一阶段崩了。最直接的办法是把整个管道按逻辑切片,用 $facet 并行跑各段,再单独 inspect 每个子结果:
-
$facet不会改变原始数据流结构,适合临时插入在任意两个阶段之间做“快照” - 别在生产环境长期保留
$facet,它会显著增加内存开销,尤其当输入文档多或字段大时 - 示例:想查
$lookup后$unwind报错原因,可这样插在中间:{ $facet: { "before_unwind": [ { $match: {} } ], "after_lookup": [ { $project: { _id: 1, items: "$items" } } ] } }
替换 $group 或 $project 为 $limit + $addFields 快速验证字段存在性
很多“空结果”或“$unwind can't apply to null”其实源于上游字段缺失或类型错乱,但 $group 会直接丢弃不匹配文档,掩盖问题。
- 把可疑的
$group临时换成$limit: 10+$addFields,显式打印关键字段值:{ $addFields: { debug_info: { input_id: "$_id", items_type: { $type: "$items" }, items_len: { $size: "$items" } } } } -
$type和$size是低成本诊断函数,比$cond嵌套更易读、更少出错 - 注意:如果字段可能为
null或缺失,$size会报错,得先用$ifNull包一层
用 mongosh 的 db.collection.aggregate(..., { explain: "executionStats" }) 查看实际执行路径
有些错误不是语法问题,而是优化器重写了管道顺序,导致语义变化——比如把 $match 提前后过滤掉了本该参与 $lookup 的字段。
-
explain: "executionStats"会返回真实执行的 stage 列表,注意看stage字段是否出现IXSCAN、COLLSCAN或意外的SHARD_MERGE - 若看到
"nReturned": 0但输入有数据,大概率是$match条件写成了字符串字面量(如"status": "active")而没加$eq,MongoDB 会静默忽略该条件 - 避免在
explain中使用$out或$merge,它们会被拒绝执行
在 Node.js 驱动中用 cursor.toArray() 替代流式消费来捕获首个错误
用 forEach 或 on("data") 处理聚合结果时,错误常发生在中间批次,但错误回调不带上下文,无法定位是第几个文档触发的问题。
- 改用
await cursor.toArray(),让整个结果集一次性抛出错误,配合try/catch可拿到完整堆栈 - 如果数据量大怕 OOM,至少加个
$limit: 100再toArray(),足够定位多数逻辑错误 - 注意驱动版本:4.15+ 的
toArray()会保留原始错误信息;旧版可能只报Cursor is closed,需升级
真正麻烦的不是语法错,而是某个 $cond 分支返回了 null,被后续 $sum 当成 0 处理,最后结果偏差却无提示——这种得靠 $addFields 把中间计算步骤全打出来,一行行对。


















