insertMany默认ordered=true导致遇重复即停,必须设ordered=false才能跳过冲突继续插入;唯一索引仅约束不提供触发器,去重需索引+写入策略配合。

唯一索引本身不支持 onInsert 触发器,MongoDB 原生没有 onInsert 这类触发机制 —— 所有“插入时自动去重”必须靠索引约束 + 写入策略配合实现,不是靠事件回调。
唯一索引创建后,insertMany 为什么还会中断?
默认 ordered: true:遇到第一个重复文档就停止后续插入,整个操作失败,BulkWriteError 抛出但只含一个错误项。
必须显式传 {ordered: false},才能跳过冲突文档继续写入剩余数据。
- Python(pymongo):
collection.insertMany(docs, ordered=False) - Node.js(mongodb driver):
collection.insertMany(docs, {ordered: false}) - Shell:
db.coll.insertMany([...], {ordered: false})
不设 ordered: false,哪怕加了唯一索引,也达不到“自动过滤重复”的效果,只是把错误从“全失败”变成“单点失败”。
如何从错误中精准定位哪个字段重复?
错误码 11000 是唯一索引冲突的统一标识,但字段名藏在不同位置,取决于驱动和版本:
- Go(mongo-go-driver):
writeException.WriteErrors[0].KeyPattern直接返回类似{"email": 1}的 map - Python(pymongo):
exc.details["keyPattern"]可读;exc.details["keyValue"]还能拿到实际冲突值 - Node.js:
err.errmsg是字符串,需正则提取,例如匹配"email_1 dup key"中的email
别依赖 errmsg 解析字段名——它格式不稳定,尤其在多语言环境或升级后可能变化;优先用 KeyPattern 或 details 字段。
为什么不能先 find 再 insert?
这是最常踩的坑:应用层查一遍再插,看似安全,实则竞态高发。两个并发请求同时查到“不存在”,然后都执行 insert,必然至少一个成功写入重复数据。
- 唯一索引 +
ordered: false是原子性保障,数据库层面强制排他 - Driver 默认可能开启重试(尤其副本集),
find + insert模式下重试会放大竞态风险 - 性能上,查+插是两次网络往返;直接 insert 是一次,且索引查找比全集合 scan 快得多
除非业务明确要求“插入前必须返回是否已存在”,否则一律跳过前置查询。
海量数据导入时,唯一索引要提前建还是边插边建?
必须提前建好。边插边建索引会导致:
- 建索引过程阻塞写入(除非用
background: true,但 background 建索引仍会锁表并显著拖慢插入) - 如果已有重复数据,
createIndex({x:1}, {unique:true})会直接失败,报错11000或12582(duplicate key) - 大数据量下,建索引耗时长,期间无法写入,运维窗口难控制
正确流程:清空集合 → 创建唯一索引 → 导入数据(mongoimport --upsert 或代码调用 insertMany(..., {ordered:false}))。注意:索引字段不能是数组(除非是多键索引且你清楚语义),否则可能意外允许“部分重复”。


















