要重试,但必须带指数退避和最大重试次数限制;仅对 TransientTransactionError 重试,用 err.hasErrorLabel('TransientTransactionError') 判断,每次重试需新建 session,并做幂等校验。

遇到 TransientTransactionError 到底要不要重试
要重试,但必须带指数退避和最大重试次数限制。MongoDB 的 TransientTransactionError 表示事务因临时性原因(如主节点切换、网络抖动、锁等待超时)失败,不是业务逻辑错误,也不是数据冲突——它明确告诉你“这次不行,稍后再试可能就成”。不重试等于主动放弃一致性;无脑重试又可能放大集群压力。
- 只对
TransientTransactionError重试,其他错误(比如WriteConflictError、UnknownTransactionCommitResult)需单独判断 - 重试前检查事务是否已提交成功(通过
commitTransaction返回结果或后续读取验证),避免重复执行副作用 - 推荐初始延迟 100ms,每次翻倍,上限不超过 1s,总重试不超过 3 次
Node.js 驱动里怎么捕获并区分这个错误
MongoDB Node.js 驱动(v4+)把 TransientTransactionError 作为独立错误类抛出,但它继承自 MongoServerError,不能靠 err.name === 'TransientTransactionError' 安全判断——有些版本会返回字符串名,有些是实例类型。稳妥做法是用 err.hasErrorLabel('TransientTransactionError')。
try {
await session.withTransaction(async () => {
await collection.updateOne({ _id: 1 }, { $inc: { count: 1 } }, { session });
});
} catch (err) {
if (err.hasErrorLabel('TransientTransactionError')) {
// 执行带退避的重试逻辑
} else {
throw err;
}
}
- 别依赖
err.code:不同 MongoDB 版本返回的错误码可能不同(如 112、251、262),不可靠 -
err.hasErrorLabel()是驱动层封装的语义判断,比字符串匹配或码值判断更健壮 - 确保你用的是 driver v4.13+ 或 v5.x,旧版本可能不支持该方法
重试时 session 能不能复用
不能复用同一个 session 对象。事务一旦失败(无论是否抛出 TransientTransactionError),该 session 就进入无效状态,再次调用 withTransaction 或 commitTransaction 会直接报 InvalidSession 或 NoSuchTransaction。
- 每次重试都必须新建
session:用client.startSession()重新获取 - 不要在重试外层 hold 住 session 并反复传入——这常见于想“节省开销”的误操作
- 如果事务涉及多个集合,且你手动管理 session 生命周期,记得在重试前显式
session.endSession()
为什么加了重试还是偶尔丢数据
根本原因常是没处理好“已提交但未感知”的情况。比如网络分区导致客户端收不到 commit 响应,驱动抛出 TransientTransactionError,你重试了,但其实上一次事务早已在服务端成功提交。
- 关键动作:重试前先做一次幂等校验。例如更新订单状态时,先查当前
status是否已是目标值 - 避免在事务内生成非幂等副作用(如发消息、调第三方 API),这类操作必须挪到事务外,并用本地事务 ID + 状态表实现去重
- 注意
readConcern: 'snapshot'和writeConcern: { w: 'majority' }必须同时启用,否则重试时可能读到脏数据或写丢失
分布式事务的复杂性不在语法,而在对“失败”这件事的诚实认知:它永远不保证一次成功,只保证你有机会按规则把它做对。漏掉幂等校验、复用 session、忽略 error label —— 这三处错任意一个,都会让重试从保险丝变成定时炸弹。










