$set 和 $addFields 完全等价,均为向文档添加或覆盖字段且默认保留所有原字段;$$ROOT 可在二者中安全引用当前文档完整副本用于备份或嵌套挂载,而 $replaceRoot 是阶段级根替换操作,不可替代 $$ROOT 用途。

$set 和 $addFields 在语法和语义上完全等价
从 MongoDB 4.2 开始,$set 就是 $addFields 的别名——它们底层行为一致,都用于向文档添加新字段或覆盖现有字段,且默认保留所有输入字段(不会像 $project 那样丢字段)。
你写 {$set: {a: 1}} 或 {$addFields: {a: 1}},效果一模一样。官方文档已明确标注 $set 是 $addFields 的“语义别名”。
实际使用中建议统一用 $set:更短、更贴近“设置字段”的直觉,也和 update 操作中的 $set 保持命名一致性。
为什么不能用 $project 实现相同效果?
$project 默认只保留你显式列出的字段,不写 _id: 1 还会隐式去掉 _id,极易意外丢数据。
- 错误写法:
{$project: {name: "$name", status: {$cond: [...]}}}→ 其他所有字段(如email、created_at)全没了 - 正确但啰嗦:
{$project: {name: "$name", email: "$email", created_at: "$created_at", status: {$cond: [...]}}}→ 字段一多就漏、一换集合就崩 - 推荐替代:
{$set: {status: {$cond: [...]}}}→ 只改想要的,其余不动
$$ROOT 在 $set / $addFields 中怎么安全使用?
$$ROOT 是当前文档的完整副本,只能在支持表达式的阶段(如 $set、$addFields、$project)里当值用,不能在 $match 或 $sort 里直接引用。
常见用途是“备份原始文档”或“嵌套挂载”,例如:
{ $set: { original: "$$ROOT" } }
注意两点:
- 这不是深拷贝操作——
$$ROOT是引用,但聚合引擎内部会按需复制,实际效果等同于深拷贝 - 大文档慎用:如果单个文档几百 KB,又在高频聚合里反复
$set: {backup: "$$ROOT"},内存和网络开销会明显上升
容易混淆的 $replaceRoot 不是 $$ROOT 的替代品
$replaceRoot 完全不是用来“保留字段”的——它会把整个文档结构替换成你指定的字段值,之后所有阶段都基于这个新根节点运行。
典型误用:
{ $replaceRoot: { newRoot: "$$ROOT" } }
看起来像“透传”,实则多余且危险:它强制重设根,可能破坏后续 $lookup 或 $unwind 的路径解析,尤其在嵌套 pipeline 里容易引发字段找不到错误。
真正想“加字段+保原样”,就老实用 $set 或 $addFields,别碰 $replaceRoot。
最常被忽略的是作用域:$$ROOT 只在表达式上下文生效,而 $replaceRoot 是阶段级指令——名字像,但根本不在一个抽象层级上。

















