commitTransaction 失败时不统一抛 MongoServerError,而是分层抛出 MongoTransientTransactionError、MongoCommitError、MongoNetworkError 或带 code 112/251 的 MongoServerError;关键依据是 error.code 和 error.errorLabels。

事务提交失败时 commitTransaction 抛什么错误?
MongoDB 驱动(以 Node.js 的 mongodb v6+ 为例)在调用 commitTransaction 失败时,**不会统一抛 MongoServerError,而是根据错误根源分层抛出不同构造的错误对象**。常见的是 MongoTransientTransactionError(可重试)、MongoCommitError(不可重试)、MongoNetworkError 或原生 MongoServerError(如错误码 112、251)。关键不是看错误名,而是查 error.code 和 error.errorLabels。
-
error.code === 112:表示“无法提交事务”,通常是写冲突或会话过期,属于可重试场景(但需检查是否已标记TransientTransactionError) -
error.code === 251:即InvalidOptions,多因传了非法选项(比如readConcern在非 majority 模式下用了linearizable) -
error.errorLabels?.includes('TransientTransactionError')是比判断name更可靠的重试依据——有些驱动版本会把临时错误包装成MongoServerError但打上这个 label - 别依赖
error.name === 'MongoCommitError'做分支逻辑,它只在 commit 阶段明确失败时出现,而很多前置失败(如操作中 abort)根本不会走到 commit
如何从 abortTransaction 或隐式中断里拿到错误码?
事务没显式 commit 就结束(比如抛异常、连接断开、超时),驱动会自动触发 abortTransaction,但这个过程**不抛错、不返回错误码**——它静默完成。你真正能捕获错误码的地方只有两个:操作执行阶段(如 collection.insertOne)、以及显式 commitTransaction 调用点。
- 所有对集合的 CRUD 操作(
insertOne、updateMany等)一旦失败,错误对象里的code就是核心线索,例如13(Unauthorized)、16500(TooManyMatchingDocuments)、133(WriteConflict) - 如果事务中某步抛
WriteConflict(code: 112不是它,code: 133才是),驱动通常会自动重试该操作(若开启retryWrites: true),但不会重试整个事务;你要自己 catch 并决定是否重跑事务 -
maxTimeMS设置过短会导致事务内任意操作超时,报MaxTimeMSExpired(code: 50),这个错误发生在具体命令层面,不是 commit 阶段
错误码 112 到底是提交失败还是写冲突?
112 是 MongoDB 里最易混淆的错误码:它名义叫 NoSuchTransaction,但实际覆盖三种完全不同的底层原因——会话丢失、事务过期、写冲突被回滚。**不能单靠 code 判断能否重试,必须结合 error.errorLabels 和上下文**。
- 含
TransientTransactionErrorlabel → 可安全重试整个事务(例如网络抖动导致 commit 请求未达服务器) - 含
UnknownTransactionCommitResultlabel → 服务器可能已提交,但客户端没收到响应;此时重试 commit 有风险(可能重复提交),应先查业务状态再决定 - 不含上述 label,但
error.message含"transaction is not active"或"no transaction started"→ 事务早已被 abort,当前 commit 无效,不应重试 - 注意:v6.7+ 驱动开始将部分写冲突归为
133,但某些分片集群场景仍可能以112返回,所以 label 比 code 更可信
在日志里稳定提取错误码的最小代码模式
不要在每个操作后手动 try/catch 提取 code,而应在事务 session 上挂载统一错误处理器。Node.js 驱动不支持全局钩子,但可以用包装函数收口:
async function withTransaction<T>(
client: MongoClient,
fn: (session: ClientSession) => Promise<T>
): Promise<T> {
const session = client.startSession();
try {
await session.withTransaction(async () => {
return await fn(session);
});
} catch (err) {
// 这里 err 是 commit 阶段或内部 abort 触发的最终错误
const code = (err as any)?.code ?? null;
const labels = (err as any)?.errorLabels ?? [];
console.error('TX failed', { code, labels, stack: (err as Error).stack });
throw err;
} finally {
await session.endSession();
}
}
- 这个模式确保所有事务级错误都经过同一出口,避免漏捕
commit错误 - 不要在
fn内部单独 try/catch 每个数据库调用——那样会打断驱动的自动重试逻辑(比如对133的重试) - 如果需要记录操作级错误码(如 insert 失败的
13),只能在fn内部加 catch,并把 code 注入 context,再由外层统一打日志 - 注意
session.withTransaction本身不抛错,它把内部错误透传出来;真正的错误源头永远在你的fn执行过程或最后 commit 步骤


















