$set本身无条件写入,不会将缺失字段设为null;误现null实因上游显式赋值或变量为null。真正只给缺失字段赋默认值,须用{status:{$exists:false}}作更新条件。

为什么直接用 $set 就能批量加字段,却常被误加成 null?
因为 $set 本身不关心文档是否存在目标字段——它只是“无条件写入”。很多人写 { $set: { status: "active" } } 后发现部分文档的 status 变成了 null,其实是上游逻辑把 status 显式设为了 null,或者用了 $set 配合了带 null 值的变量。MongoDB 不会主动把缺失字段补成 null,它只按你传的值写。
如何真正“只给缺失字段赋默认值”,避免覆盖已有值?
必须配合查询条件过滤出“字段不存在”的文档,不能靠 $set 自己判断。正确姿势是:用 { field: { $exists: false } } 作为更新条件。
db.users.updateMany({ status: { $exists: false } }, { $set: { status: "active" } })- 如果字段可能为
null或空字符串,还想一并覆盖,就得叠加条件:{ $or: [ { status: { $exists: false } }, { status: null }, { status: "" } ] } - 注意:用
$exists: false不会匹配字段值为null的文档——MongoDB 把null当作一个合法值,字段依然“存在”
在聚合管道中用 $set 批量加字段时,$cond 和 $ifNull 怎么选?
聚合阶段的 $set(即 $addFields 的别名)支持表达式,但语义和 update 中的 $set 不同:它总是计算并写入,不带条件过滤能力。所以得靠表达式内部做判断。
- 用
$ifNull: ["$status", "active"]:仅当字段为null或缺失时取默认值;字段值为0、false、""也会被替换(因为它们在 BSON 中不是null,但$ifNull只认null和不存在) - 用
$cond更精确:{ if: { $eq: [{ $type: "$status" }, "missing"] }, then: "active", else: "$status" }—— 这才真正只处理缺失字段 - 性能上,
$ifNull更轻量;需要区分null和缺失时,必须用$cond+$type
更新大量文档时,为什么加了 { multi: true } 还只改了一条?
这是 MongoDB 3.2+ 的常见误解:update() 方法已废弃,updateMany() 才是标准接口。如果你还在用旧版驱动或 shell 写 db.col.update(..., { multi: true }),而没升级语法,很可能因版本差异导致行为不一致。
- 务必使用
updateMany()(Node.js driver、mongosh、compass 全支持) - 检查返回结果中的
matchedCount和modifiedCount:前者是满足条件的文档数,后者是实际值被改变的文档数;若两者相等,说明所有匹配文档都确实被写入了新字段 - 对千万级集合,建议加
{ writeConcern: { w: 1 } }避免主从延迟导致误判,同时考虑加索引:db.col.createIndex({ status: 1 }, { partialFilterExpression: { status: { $exists: false } } })加速后续同类操作
undefined,也不等价于 null,更不会因为没声明就自动有默认值。每次用 $set 批量加字段前,先想清楚你要覆盖的是“不存在”,还是“为空”,或是“为假值”,选错条件,后面查起来就全是坑。


















