bulkWrite失败后需设{ordered: false},通过result.writeErrors数组获取具体失败项的index、code(如11000)和errmsg,而非依赖try/catch或result.nInserted等字段。

bulkWrite失败后怎么拿到具体哪几条没写进去
bulkWrite默认是ordered: true,一遇到重复键(E11000)或类型错误就停,后续操作全跳过,你根本看不到“哪些成功了、哪些失败了”。必须显式设{ ordered: false },它才会把所有操作都试一遍,再统一返回结果。
关键不是看result.ok或result.nInserted——它们只告诉你执行流程走完了,不反映语义错误。真正要盯的是result.writeErrors数组:
- 每个元素含
index(对应原始输入数组下标)、code(如11000)、errmsg(比如dup key: { email: "a@b.com" }) -
code === 11000表示唯一键冲突,可提取字段名做业务提示 -
code === 121是Schema验证失败,得查模型里required或validate规则是否被绕过
insertMany报错时为什么try/catch捕不到DuplicateKeyError
Node.js MongoDB驱动对insertMany的设计就是“失败不抛异常”,尤其在{ ordered: false }下:即使有E11000,err仍是null,整个try/catch块完全无效。你以为进了catch,其实连catch的门都没摸到。
正确入口只有两个:
- 用
insertMany时,检查返回的error.writeErrors(注意:不是error.errors) - 用
save()或insertOne()时,try/catch才真正生效,但必须捕获MongoServerError,而不是笼统的Error - PyMongo用户注意:
DuplicateKeyError不继承自Exception,得显式from pymongo.errors import DuplicateKeyError再except DuplicateKeyError:
重试前必须确认_id类型一致,否则永远重试失败
前端传来的_id是字符串"507f1f77bcf86cd799439011",后端没转成ObjectId就直接插进数据库,MongoDB会把它当字符串存,而唯一索引建在ObjectId类型上——结果就是“看着重复,实际没命中索引”,重试多少次都是新文档。
解决办法很直接:
- Mongoose模型里定义
_id: { type: Schema.Types.ObjectId, required: true }并开strict: true,让校验提前拦住类型错误 - 手动转ID时用
new ObjectId(id),别信ObjectId(id)的旧写法(新版驱动已弃用) - 查重逻辑也得用同类型:
collection.findOne({ _id: new ObjectId(id) }),不能混用字符串和ObjectId
别用upsert代替insert来“自动去重”
upsert: true看似省事,但它本质是“先查再判”,每次都要多一次索引查找。高并发下容易引发写冲突,更危险的是语义污染:你只想插入新数据,它却可能意外覆盖已有字段。
比如updateOne({ _id }, { $set: doc }, { upsert: true }),如果doc漏了某个字段,那个字段在原文档里就会被清空——这不是重试,是数据破坏。
真要跳过重复项,就老实用bulkWrite + { ordered: false } + 检查writeErrors。重试时只挑出code !== 11000的那几条,或者对11000单独做业务降级(比如返回409 Conflict并附带冲突字段名)。


















