bulkwrite insert 遇 duplicate key 不中断执行需设 ordered: false,通过 result.writeerrors 检查 11000 错误并定位失败项;不可用 upsert 替代,避免误更新和性能损耗;务必统一 _id 类型防伪重复。

bulkWrite insert 时遇到 duplicate key 怎么不中断执行
默认情况下,bulkWrite 中任意一条 insertOne 或 insertMany 触发重复键(duplicate key)错误,整个操作会立即终止,后续语句全被跳过。这不是“忽略”,是硬性失败。
真正能跳过重复项继续执行的,只有靠 ordered: false + 合理处理错误结果:
-
ordered: true(默认):遇到第一个E11000 duplicate key就停,返回已执行成功的条目数 + 错误 -
ordered: false:所有操作并行尝试,重复键只让那条失败,其余照常;最终通过result.upsertedCount、result.insertedCount和result.writeErrors分别查看成败
如何判断哪几条插入因重复键失败
bulkWrite 返回对象里,writeErrors 字段才是关键——它是个数组,每个元素含 index(对应入参数组下标)、code(如 11000)、errmsg(含具体字段名,比如 dup key: { username: "admin" })。
别直接看 result.ok 或抛异常:即使有失败,只要没崩,ok 仍是 1;Node.js 驱动也不会自动 throw,得手动检查 writeErrors:
const result = await collection.bulkWrite(ops, { ordered: false });
if (result.writeErrors.length > 0) {
result.writeErrors.forEach(err => {
if (err.code === 11000) {
console.log(`第 ${err.index} 条因重复键跳过:${err.errmsg}`);
}
});
}
为什么不能用 upsert 替代 insert 来“自动去重”
upsert: true 看似能避免报错,但它本质是“查无则插,有则更新”,而你只想插入新数据、对已有数据完全不碰——一旦误配 filter 或漏写 update 字段,可能意外覆盖原有字段,甚至把 { _id: xxx } 当成 filter 导致全量更新。
更危险的是性能:每个 upsert 都要先查再判,比纯 insert 多一次索引查找;高并发下还可能引发写冲突或文档膨胀(尤其用了 $setOnInsert 但逻辑没兜住)。
- 真要“存在就跳过”,
ordered: false+insertOne是最轻量、语义最干净的方式 - 若必须用 upsert,请确保
filter精确匹配唯一键,且update为空对象或仅含$setOnInsert
常见踩坑:_id 类型不一致导致看似重复实则没命中索引
比如前端传来字符串 "507f1f77bcf86cd799439011",后端没转成 ObjectId 就直接插进 _id 字段,MongoDB 会存成字符串而非 ObjectId —— 即使表里已有该值的 ObjectId 文档,这次插入仍会成功(类型不同),但下次用 ObjectId("...") 查就查不到。
这种“伪重复”不会触发 11000 错误,却破坏数据一致性。务必统一 _id 类型:
- 入库前用
ObjectId.isValid()校验,再用new ObjectId(str)转换 - 在 schema 层(如 Mongoose)显式声明
_id: { type: ObjectId } - 聚合查询或
find时,也保持类型一致,别混用字符串和 ObjectId
重复键错误本身好处理,但类型错位带来的静默不一致,才是线上最难 debug 的点。










