首页/数据/NoSQL 速查/upsert 不存在则插入
MongoDB99 条

upsert 不存在则插入

updateOne/updateMany 传 {upsert:true}:无匹配文档时按“过滤条件 + 更新内容”新建;$setOnInsert 只在插入分支生效。

NoSQL 速查数据更新于 2026-10-13

upsert 是什么

updateOne 或 updateMany 传 { upsert: true } 之后,如果没有任何文档匹配过滤条件,就按「过滤条件 + 更新内容」新建一条;只希望在新建分支写入的字段用 $setOnInsert 包裹。它把「查不到就插入」的两步操作压成一次请求,但并发安全仍要依赖唯一索引。

基本信息

选项{ upsert: true }
仅插入时生效$setOnInsert
返回字段upsertedId 标识新建文档,upsertedCount 为新建数量
插入分支构造过滤条件中的等值字段会合并进新文档
并发保障业务键必须建唯一索引,否则可能插入重复文档

典型使用场景

  • 计数器:文档不存在则从初始值开始累加
  • 配置初始化:首次写入默认配置,之后只做更新
  • 用户偏好记录:登录时不存在则创建
  • 幂等导入:按业务键写入,重复执行不产生重复文档
  • 元数据维护:不存在时顺带补建一条记录

常用写法

常用写法
// 计数场景:不存在则插入,存在则自增
 db.counters.updateOne(
   { name: "orderNo" },
   { $inc: { value: 1 }, $setOnInsert: { createdAt: new Date() } },
   { upsert: true }
 )

// 返回结果解读:upsertedId 非空表示发生了插入
 const r = db.users.updateOne({ email: "a@example.com" }, { $set: { lastLogin: new Date() } }, { upsert: true })
 print(r.upsertedId, r.matchedCount, r.modifiedCount)

// 并发安全的前提是唯一索引
 db.users.createIndex({ email: 1 }, { unique: true })

返回字段读法

字段含义怎么用
matchedCount匹配到的文档数为 0 时结合 upsertedId 判断是否走了插入分支
modifiedCount实际被修改的文档数写入内容与现值相同时可能为 0
upsertedId新建文档的 _id非空即表示本次是插入
upsertedCount新建文档数量upsert 最多新建一条
插入分支的文档是「过滤条件中的等值字段」与「更新内容」的合并结果,更新操作符在插入分支同样会被应用;如果过滤条件里写了范围、$or 之类非等值条件,这些条件不会进入新文档。
只应出现在新建文档里的字段(创建时间、初始状态、来源标记)一律用 $setOnInsert 包裹;和 $set 混用时要确保两者操作的字段不重叠,否则更新语句本身就会报错。

易错点

  • 没有唯一索引保护的并发 upsert 可能插入多条重复文档,计数类业务键必须建唯一索引
  • upsert 新建时与已有唯一索引值冲突会抛 E11000,客户端需要捕获该错误并改用更新重试
  • 把 $setOnInsert 与 $set 写在同一个字段上,插入分支中两个操作符冲突会直接报错
  • 误以为 upsert 是原子的「查不到就插入」:updateMany 配 upsert 在没有匹配时只会新建一条,也不能替代唯一约束

数据来源与参考

  • MongoDB Manual: upsert
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌