upsert通过filter查询是否至少匹配一个文档来判断“有”或“无”,匹配则更新、否则插入;filter必须稳定唯一,推荐始终用$set避免覆盖,_id需谨慎处理以防重复或报错。

upsert 是怎么判断“有”还是“无”的
本质就一条:MongoDB 用 filter 参数里的查询条件去查集合,只要找到**至少一个匹配文档**,就走更新(update);否则走插入(insert)。它不看字段值是否“真正变化”,也不管你 update 操作里改了啥,只认 filter 是否命中。
常见错误现象:upsert: true 却反复插入新文档——大概率是 filter 写成了动态值(比如带 Date.now() 或随机数),导致每次条件都不一样,永远查不到。
- 使用场景:用户资料初始化、计数器初始化、配置项兜底写入
- 关键点:
filter必须是稳定、可复现的唯一标识,比如{ userId: "u123" },而不是{ createdAt: { $gt: new Date() } } - 性能影响:如果
filter字段没建索引,每次 upsert 都会全表扫描,写入延迟飙升
update 参数里写 $set 还是直接写字段值
取决于你是否想保留原文档其他字段。MongoDB 的 updateOne(含 upsert)默认是「替换式更新」,但加了 $set 就变成「局部更新」。
示例对比:
db.users.updateOne(
{ userId: "u123" },
{ name: "Alice", age: 30 }, // ❌ 无 $set:upsert 时插入完整文档,但更新时会删掉原 doc 其他字段(如 email)
{ upsert: true }
)
db.users.updateOne(
{ userId: "u123" },
{ $set: { name: "Alice", age: 30 } }, // ✅ 有 $set:无论插入还是更新,都只动指定字段
{ upsert: true }
)
- 推荐始终用
$set,避免意外覆盖 - 如果真要替换整个文档(比如 schema 重构),用
replaceOne更明确,别靠 updateOne 的无操作符行为 - 注意:
$set在 upsert 插入阶段等价于直接赋值,不影响行为,但语义更安全
为什么 _id 字段在 upsert 中特别容易出错
_id 是 MongoDB 的硬性约束字段,upsert 时如果 filter 不含 _id,而 update 操作又试图写入 _id,就会报错:Performing an insert operation on a collection with a field named _id。
根本原因:upsert 插入分支会尝试把 update 部分的字段直接当新文档内容,若含 _id,就等于手动指定 _id —— 这要求你确保该 _id 绝对不重复,且类型合法(不能是 undefined 或空对象)。
- 安全做法:让 MongoDB 自动生成
_id(即 filter 不含 _id,update 也不显式设 _id) - 必须自定义 _id?那就把 _id 放进
filter里,比如{ _id: ObjectId("...") },这样 upsert 才知道“按这个 _id 查,没有就插这个 _id” - 常见坑:用
new ObjectId()生成新 id 写进 update,结果每次都不一样,等同于没 upsert
upsert 和 findAndModify 的原子性差异
updateOne + upsert: true 是服务端原子操作,但仅限单文档。如果你需要「查+改+返回旧值」或「多条件竞态控制」,就得用 findOneAndUpdate,它底层调用的是和 upsert 同一引擎,但多了返回控制。
错误认知:“upsert 能防止并发插入重复数据”——它只能防同一 filter 的并发,如果两个请求的 filter 看似不同但业务上等价(比如用邮箱和手机号分别查用户),MongoDB 无法识别,仍可能插入两条。
- 并发安全的前提:filter 必须能精确表达业务唯一性,且对应字段有唯一索引(
unique: true) - 没唯一索引?即使 upsert 也挡不住两个请求同时判定“不存在”,然后都执行插入
- 真正要强一致性,得配合应用层锁或数据库事务(4.0+ 副本集/5.0+ 分片集群支持 multi-document transaction)
最常被忽略的一点:upsert 的“存在判断”和“插入动作”之间没有间隙,但你的应用逻辑如果在 upsert 前先做了一次 find,再决定要不要 upsert,那就彻底失去原子性了——那不是 upsert,那是自己手写竞态。










