e11000重复键错误是mongodb对唯一索引冲突的必然报错,根本原因在于数据与现有唯一索引(如_id、username或复合索引)不匹配,而非bulkwrite逻辑缺陷;常见诱因包括未清旧数据、对象引用复用、手动_id不唯一、upsert条件匹配多文档等。

重复键错误不是插入逻辑的问题,而是索引和数据状态不匹配的必然结果
bulkWrite() 遇到 E11000 duplicate key error 的真实原因
这个错误出现时,bulkWrite() 并没有“失败”,它只是忠实地报告了违反唯一约束的事实。关键点在于:MongoDB 在执行 insertOne 或 replaceOne 时,会检查所有已存在的唯一索引(包括 _id、userName、复合索引如 {h_id: 1, date: 1}),一旦发现待写入文档的对应字段值已在集合中存在,就立刻抛出 E11000 duplicate key error collection: xxx index: yyy。
常见诱因包括:
- 批量操作前未清理旧数据,而新数据含相同
userName或h_id - 代码中复用同一个对象引用(比如用
yield返回未深拷贝的对象),导致多个insertOne操作实际提交的是同一份内存数据 - 手动设置了
_id字段但未保证其全局唯一(例如用固定字符串或时间戳截断值) - 使用
upsert: true但查询条件匹配到多个文档,触发了底层唯一性校验冲突
createIndex 创建唯一索引时的两个硬性前提
调用 createIndex({ h_id: 1, date: 1 }, { unique: true }) 不会自动“修复”已有重复数据——它只会拒绝创建,并报错类似:(IndexKeySpecsConflict) An existing index has the same name... 或更常见的 duplicate key error。
必须先满足:
提供端到端的高端代理设置和使用指导,包括代理类型选择、地区定向、访问配置、API密钥验证和监控。
- 集合中不存在任何两份文档同时拥有相同的
h_id和date值(可用db.hole_status_info.aggregate([{$group: {_id: {h_id: "$h_id", date: "$date"}, count: {$sum: 1}}}, {$match: {count: {$gt: 1}}}])检查) - 若已有非唯一索引同名(如
h_id_1_date_1),需先用db.hole_status_info.dropIndex("h_id_1_date_1")删除,再重建;或显式指定新名:{ name: "h_id_date_unique_v2" }
如何让 bulkWrite 安全绕过重复键中断
默认 ordered: true 意味着只要第一个 insertOne 报 E11000,后续所有操作全部跳过。这不是容错,是阻断。
改用无序模式可继续执行其余操作,但要注意副作用:
- 设
ordered: false后,bulkWrite()返回结果中writeErrors数组会列出所有失败项,需主动遍历处理 - 不能依赖操作顺序——比如先
updateOne再deleteOne,无序下可能删掉还没更新的文档 - 对有业务强依赖关系的操作组合(如“先插入用户,再插入其订单”),绝不可设为无序
示例写法:
collection.bulkWrite([
{ insertOne: { document: { userName: "demoJx", email: "a@b.com" } } },
{ updateOne: { filter: { userName: "other" }, update: { $set: { status: "active" } } } }
], { ordered: false })
真正该检查的三个地方
别只盯着代码里有没有 unique: true——90% 的重复键问题根子不在索引定义,而在数据流源头:
-
_id字段是否被手动赋值?如果用了new ObjectId("...")但字符串重复,或直接写死"123",就会触发dup key: { _id: ObjectId('...') } - 前端是否重复提交?没禁用按钮、没清空表单、没加防抖,
bulkWrite就成了“批量报错” - 数据库迁移或脚本重跑时,是否忘了加
deleteMany({})清场?尤其在开发/测试环境反复运行初始化脚本极易中招
最隐蔽的坑:用 InsertMany 传入一个数组,但数组里多个元素引用的是同一个 JS 对象——表面看是 10 条数据,底层却是 10 个指向同一内存地址的指针。










