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 混用时要确保两者操作的字段不重叠,否则更新语句本身就会报错。