先说一个核心判断:insertMany 写入失败时,并不会抛出一个统一的错误类型。你可能会遇到 MongoWriteConcernError、MongoBulkWriteError、MongoServerError(比如 code 11000 代表重复键),甚至原生 Error(网络中断)。关键不在于看 error.name,而是要盯住 error.code 和 error.writeErrors——对于批量写入来说,后者尤其重要。

写入失败时 insertMany 抛什么错误?
实际上,MongoDB 驱动会根据具体失败原因返回不同错误对象:写关注没满足,给你 MongoWriteConcernError;批量写入中部分失败,给你 MongoBulkWriteError;常规服务端错误如重复键,则是带 code: 11000 的 MongoServerError;当然还有网络中断这种原生 Error。所以判断逻辑要围绕 error.code 和 error.writeErrors 展开,别只盯着 error.name——它往往不靠谱。
为什么 try/catch 捕不到“控制台没输出”?
这种情况十有八九是变量名引用问题。举个例子:你明明用 const Model = require("./models/match") 导入,结果在路由里写了 await Match.insertMany(...)。那好,Match is not defined 这个 ReferenceError 在函数刚开始执行时就被抛出了——同步语法错误,try/catch 根本抓不住,因为整个 async 函数体还没走到 await 那一行,当然不会有任何输出。
所以检查顺序很简单:
- 核对模型中使用的变量名和
require时定义的名字是否完全一致,大小写、拼写都不能马虎。 - 确认模型已正确导出,没有丢三落四。
- 在
catch块里加上console.error(error.stack)而不是只打印error.message——堆栈信息能告诉你错在哪一行。
insertMany 部分失败时怎么拿到具体哪几条错了?
insertMany 默认走 ordered: true,即遇到第一条错误就立刻刹车。但只要改成 ordered: false,它就会继续处理剩余数据,最后通过 error.writeErrors 数组把每条失败项的索引、错误码和详情全抛给你:
try { const result = await Model.insertMany(data, { ordered: false });} catch (error) { if (error.writeErrors) { error.writeErrors.forEach(({ index, code, errmsg }) => { console.warn(`第 ${index} 条数据写入失败(code ${code}):`, errmsg); }); }}
这里有几个常见错误码值得记住:
code: 11000—— 唯一键冲突,解决方法通常是去重,或者改用updateOne配合upsert。code: 121—— 文档验证失败,回头看看 schema 里的required和validate规则是不是太严了。- 另外,
ordered: false虽然不影响成功写入的数据,但错误堆栈会明显变长,生产环境最好加上日志采样,免得被海量错误信息刷屏。
事务中写入失败,commitTransaction 还会执行吗?
答案是否定的。commitTransaction 只有在显式调用时才会触发,而一旦某次 collection.insertOne 失败并抛出异常,驱动会自动 abort 当前事务,commitTransaction 那行代码根本执行不到。所以你能拿到错误码的地方只有两个:要么是 CRUD 操作本身(比如 insertOne 报 code: 133 写冲突),要么是显式调用 commitTransaction 时(可能报 code: 112 或 251)。
- 千万不要在
catch里默认事务已经提交——老老实实检查session.inTransaction()的状态。 - 注意
code: 112并不代表写冲突(写冲突是133),它更可能意味着会话过期或事务超时。这时候要结合error.errorLabels?.includes('TransientTransactionError')来判断能否重试。 - 如果事务内某个操作因为
maxTimeMS设置太短而超时,错误码是code: 50(MaxTimeMSExpired),这发生在具体命令层,可不是 commit 阶段的问题。
说到这里,核心结论就很清晰了:事务写入异常的根因往往不在 commit 那一刻,而在你第一次调用 insertOne 或 updateMany 的瞬间。错误码早就藏在那里了,别等到 commitTransaction 才去找原因。
