upsert不能替代唯一索引,它仅提供“查不到就插、查到了就更新”的原子操作,真正防止重复插入的是唯一索引;高并发下依赖upsert易触发E11000错误,唯一索引才是数据一致性的强制护栏。
u
ps
ert 不能替代唯一索引,但能规避部分重复插入场景
直接说结论:
本身不防止重复插入,它只是“查不到就插、查到了就更新”的原子操作。如果你依赖它来避免
,那大概率会在高并发下翻车——两个请求同时
没查到,然后都执行
,最终撞上唯一索引报错。
真正起兜底作用的是唯一索引(
),
只适合做业务逻辑层面的“状态同步”,比如刷新用户资料、上报设备心跳。
对性能无额外开销,但要求你明确指定查询条件(如
),且该字段必须有索引(否则慢)
如果查询条件字段没建索引,MongoDB 会全表扫描,
变成性能黑洞
配合
时,更新动作只影响匹配到的文档;若没匹配到,才插入新文档——它不保证插入数据的字段组合全局唯一
为什么 updateOne + upsert:true 仍可能抛 E11000 错误
错误常发生在你用
更新一个带唯一约束的字段,而新值与其他已有文档冲突。例如:
假设集合中已存在
的文档(哪怕
不同),这个操作就会触发
:因为
字段有唯一索引,而你试图让两条文档共享同一个
。
这不是
的 bug,是唯一索引在尽责拦截非法状态
解决路径只有两种:提前校验目标值是否已被占用;或改用
(但需传入完整文档,且仍受唯一索引约束)
别在
中更新任何带
索引的字段,除非你确认该值未被其他文档使用
批量 upsert 的坑:ordered=false 也不保险
用
批量 upsert 时,即使设了
,只要某条操作违反唯一索引,MongoDB 仍会立即终止该条操作并抛
,其余操作照常执行——听起来安全?其实不然。
go语言参考手册 中文CHM版
Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。本文给大家带来Go参考手册,需要的可以来下载! Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。 Go 语言特色 简洁、快速、安全 并行、有趣、开源 内存管理、v数组安全、编译
下载
问题在于:批量中的多条 upsert 可能命中同一查询条件(比如都查
),而 MongoDB 不保证它们的执行顺序。结果就是:其中一条成功插入,另一条因查到已存在而执行 update,但如果 update 内容又触发另一个唯一字段冲突(比如
字段也参与复合唯一索引),照样报错。
复合唯一索引(如
)会让批量 upsert 的冲突判断变复杂,必须确保每条数据的组合键真正唯一
不要指望
能“自动跳过”所有冲突;它只跳过单条失败,不解决语义冲突
真正稳妥的做法是:先用
或
预检关键字段值是否已存在,再决定走 insert 还是 update
事务内捕获 DuplicateKeyError 是无效的
在 MongoDB 事务里写类似这样的代码:
它不会按预期工作。一旦
报
,事务立刻被服务端标记为
,后续任何写操作(包括那个
)都会立刻抛
。
事务内无法“降级处理”唯一键冲突,MongoDB 不允许 abort 后继续写
可行方案只有两个:事务外提前
查重;或改用非事务逻辑,靠唯一索引 + 重试机制兜底
别在事务回调里 try/catch 插入再补 update——那行代码根本执行不到
最常被忽略的一点:唯一索引不是性能装饰品,它是数据一致性的强制护栏。删掉它来“解决” E11000,等于拆掉刹车去跑山路。
upsertE11000 duplicate key errorfindOneinsertOneunique: trueupsertupsert{ email: "a@b.com" }upsertupsert: trueupdateOneupdateOnedb.users.updateOne(
{ _id: ObjectId("...") },
{ $set: { username: "admin" } },
{ upsert: true }
){ username: "admin" }_idE11000usernameusernameupsertreplaceOne$setuniquebulkWriteordered: falseE11000{ h_id: "xxx" }date{ h_id: 1, date: 1 }ordered: falsedistinctcountDocumentsawait session.withTransaction(async () => {
try {
await collection.insertOne({ _id: "abc", name: "test" });
} catch (err) {
if (err.code === 11000) {
await collection.updateOne({ _id: "abc" }, { $set: { name: "test" } });
}
}
});insertOneE11000abortedupdateOneInvalidTransactionOperationErrorfindOne