insertMany默认中断是因为ordered:true设计为“全成功或全失败”,遇重复_id等错误即终止;设ordered:false可跳过错误继续执行,返回insertedCount和writeErrors详情。

直接设 ordered: false 即可跳过错误继续执行,不是靠 try-catch 或重试逻辑。
为什么 insertMany 默认会中断?
默认 ordered: true,只要某条文档插入失败(比如重复 _id、字段类型不符、违反唯一索引),整个批量操作立刻终止,后续所有文档都不再处理。这不是性能问题,是行为设计——它保证“要么全成功,要么全不执行”,但多数导入场景其实只需要“尽力而为”。
常见现象:BulkWriteError 报错只显示第一条失败原因;日志里看到“只插了前 12 条”,其实是第 13 条出错后直接停了。
-
insertMany返回结果里insertedCount明显小于输入数组长度,且没报错 → 很可能中间有静默失败(如_id类型不匹配) - 用
bulkWrite时没传{ ordered: false },却期望跳过重复项 → 白等半天发现数据卡在半路
insertMany + ordered: false 怎么写才安全?
这是最简方案,适合大多数“存在即跳过”的导入场景:
await collection.insertMany([
{ _id: "1", name: "Alice" },
{ _id: "2", name: "Bob" },
{ _id: "1", name: "Charlie" } // 重复 _id
], { ordered: false });
注意三点:
- 必须显式传
{ ordered: false },不能只写insertMany(docs) - 返回值里的
result.insertedCount是实际成功数,result.writeErrors包含每条失败详情(含index和errmsg) - 如果想记录哪些被跳过,别只看
writeErrors.length,要遍历并过滤err.code === 11000(重复键错误)
bulkWrite 比 insertMany 更灵活,但也更易踩坑
当你需要混合 insert / update / delete,或想对不同错误做差异化处理(比如重复键跳过、类型错误告警),bulkWrite 是更好选择:
const ops = docs.map(doc => ({ insertOne: { document: doc } }));
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}`);
}
});
}
关键差异点:
-
bulkWrite的writeErrors是完整错误列表,insertMany的错误对象只暴露第一个 - 不要用
upsert: true替代insertOne—— 它会先查再写,慢且可能意外覆盖字段 - 如果文档里
_id是字符串但集合索引是ObjectId,MongoDB 不会命中索引,导致“看似重复实则插入两份”
分批和 writeConcern 才是吞吐瓶颈的真正开关
跳过错误只是让流程不中断,但真正影响速度的是批大小和确认级别:
- 单批别超
10000条,BSON 总大小别超16MB(否则直接报DocumentTooLarge) - 生产环境建议设
writeConcern: { w: 1 }(只写主节点),避免等全部副本确认拖慢速度 - 高并发大批量插入时,禁用事务(除非跨文档强一致性必需)——事务内
insertMany容易触发WriteConflict - 如果数据本身大量随机
_id(比如 UUID),考虑先删索引、插完再建,能快几倍
最容易被忽略的点:ordered: false 下 MongoDB 无法利用索引顺序优化写入,所以前期清洗掉明显重复数据,比全靠数据库跳过更稳。

















