应重试,但仅限errorLabels含"TransientTransactionError"的错误(如NotWritablePrimary、PrimarySteppedDown);须2~3次指数退避重试,每次新建ClientSession和事务,读操作启用readConcern:"snapshot"。

TransientTransactionError 到底该不该重试
应该重试,但必须区分错误类型——不是所有带 Transient 字样的错误都安全可重试。MongoDB 官方只将明确标记 errorLabels: ["TransientTransactionError"] 的错误归为瞬态错误,比如 NotWritablePrimary、PrimarySteppedDown、NetworkTimeout。而 WriteConflict 虽然也是瞬态,但业务逻辑可能需合并或跳过;Unauthorized 或 InvalidNamespace 是配置问题,重试毫无意义。
Node.js 驱动里别手写 start/commit 循环
用官方驱动(v4.0+)时,优先走 withTransaction(),它已内置对 TransientTransactionError 和 UnknownTransactionCommitResult 的识别与重试逻辑。自己手动调 session.startTransaction() → 执行操作 → session.commitTransaction() 容易漏掉状态清理、退避控制和标签校验。
示例正确用法:
await session.withTransaction(async () => {
await collection.updateOne({ _id: 1 }, { $inc: { count: 1 } });
await logs.insertOne({ event: "update", ts: new Date() });
});
注意:withTransaction() 默认最多重试 5 次,无指数退避,生产环境建议配合自定义 maxTimeMS 和外围封装控制节奏。
重试时 session 和事务生命周期怎么管
每次重试必须新建事务上下文,不能复用失败的 session.transaction。常见错误包括:
- 在
session.transaction.state === "TRANSACTION_ABORTED"后还调commitTransaction(),直接抛NoSuchTransaction - 试图复用旧
session的事务状态,但session对象本身可复用(只要没过期),其内部事务状态不可重置 - 重试达到上限后忘记调
session.endSession(),长连接池下会累积泄漏
正确做法是:每次重试前显式调 session.startTransaction(),并在最终退出时确保 session.endSession() 被调用(尤其在 try/finally 或 promise.finally 中)。
手动封装重试时的三个硬性条件
如果业务需要精细控制(如定制退避、埋点、降级),手写重试逻辑必须满足:
- 重试次数限制为 2~3 次,第 1 次失败后等待
100ms,第 2 次失败后等待300ms,第 3 次失败直接 throw 原错误 - 每次重试都新建
ClientSession,整个事务体(含读操作)全部重放,不能只重试commit - 读操作必须加
readConcern: "snapshot",否则重试时看到的数据可能不一致
最容易被忽略的是最后一点:事务内读取结果不能缓存复用,必须在新 session 里重新读——因为两次事务执行之间数据可能已被其他写入修改。



















