$merge必须位于管道末尾且into、on、whenmatched三字段缺一不可,因mongodb强制校验其语义完整性:into指定目标集合,on定义匹配键(支持单字段或复合数组),whenmatched控制更新策略,缺失任一将直接报错。

$merge 是 MongoDB 4.2+ 增量更新物化视图的唯一可靠方式,不能用 $out 替代。 它天然支持“有则更新、无则插入”,且不破坏现有文档、不中断查询、可触发变更流。
为什么 $merge 必须放在管道末尾且不能省略 into、on、whenMatched
这三个字段缺一不可,否则会直接报错:"$merge stage requires 'into' field" 或 "'on' field is required"。MongoDB 强制校验它们的存在,是因为 $merge 的语义依赖这三者共同定义行为边界:
-
into指定目标集合,可以是字符串(如"sales_summary")或对象(如{db: "reporting", coll: "daily_metrics"}) -
on是匹配依据,必须是输入文档中真实存在的字段;若用数组(如["customerId", "date"]),所有字段都得在输出里存在 -
whenMatched决定已有文档怎么处理:常用"replace"(全量覆盖)、"merge"(仅合并指定字段)、"keepExisting"(跳过);设成"fail"会中断整个聚合
常见静默失败:输出文档不含 on 字段时 $merge 什么也不做
这是最隐蔽的坑——没有报错,也没有写入,但结果集缺失数据。典型场景包括:
- 用了
$group后只保留了_id和累加字段,却把on: "userId"对应的原始字段丢掉了 - 做了
$unwind后没显式保留原始_id或业务键,导致on: "_id"匹配失败 - 联查(
$lookup)后未用$set把关联字段注入到根层级
解决方法很简单:在 $merge 前加一个 $set 阶段确保关键字段存在。例如:
[{$group: {_id: "$category", total: {$sum: "$amount"}}}, {$set: {categoryId: "$_id"}}, {$merge: {into: "cat_summary", on: "categoryId", whenMatched: "merge", whenNotMatched: "insert"}}]
whenNotMatched: "discard" 是高危配置
这个选项会让所有不匹配的文档被直接丢弃,而不是插入新记录。一旦误配,新产生的聚合结果就彻底消失,且无日志提示。生产环境应始终用默认安全值 "insert"。
另外注意:whenMatched: "merge" 不是深合并,它只对顶层字段生效;如果想合并嵌套对象,得配合 $mergeObjects 自定义 pipeline,例如:
{$merge: {into: "user_profiles", on: "_id", whenMatched: { $mergeObjects: ["$$new", "$$old"] }, whenNotMatched: "insert"}}
测试时务必先看输出结构,再跑 $merge
执行前用 db.collection.aggregate([...stages]).toArray() 手动检查聚合输出是否含 on 字段、类型是否正确、有没有 null 值。特别是复合键场景,on: ["a", "b"] 要求每个文档都有 a 和 b 字段,且不能为 null 或缺失——MongoDB 不会帮你补默认值,也不会警告。











