Callback API 不重试 UnknownTransactionCommitResult 错误,需新建 session 并重跑全部读写操作,且必须使用 snapshot readConcern 与幂等写入,配合手动指数退避。

Callback API 不会自动重试 UnknownTransactionCommitResult
旧版事务重试逻辑(比如只捕获 TransientTransactionError 并重试整个事务)在 MongoDB 5.0+ 场景下容易漏掉关键错误。Callback API(如 PyMongo 的 with_transaction())默认只处理 TransientTransactionError,对 UnknownTransactionCommitResult 完全不重试——这不是 bug,是设计使然。这个错误表示“客户端发了 commit,但没收到明确响应”,服务端可能已提交、也可能没提交,驱动不敢替你决定。
事务提交失败后 session 状态不可恢复
遇到 UnknownTransactionCommitResult 后再调用 session.commitTransaction() 或 session.abortTransaction(),几乎必然抛出 InvalidSession。原因很直接:MongoDB 驱动在提交失败后会将 session 标记为「终止态」,不允许复用。你不能“接着上次的 session 继续”,必须:
- 显式调用
session.endSession()(尤其在连接池场景下防泄漏) - 用
client.start_session()新建 session - 重新调用
session.start_transaction() - 完整重跑所有读写操作(不是只重试 commit)
readConcern: "snapshot" 和幂等写入是硬性前提
重试时若沿用上一次读到的数据(比如缓存了账户余额),结果可能错乱。因为两次事务运行期间,外部数据可能已被修改。必须:
- 每次重试都在新 session 中重新执行
find_one_and_update()等读操作 - 显式设置
read_concern={"level": "snapshot"}(PyMongo)或等效参数,确保读取的是事务开始时的一致快照 - 写操作必须幂等:
update_one(..., upsert=True)替代insert_one();用$setOnInsert控制初始字段,避免重复插入或覆盖
退避策略必须手动实现,且不能依赖 errorLabels
UnknownTransactionCommitResult 不在官方 errorLabels 列表中,errorLabels.includes("TransientTransactionError") 对它完全无效。不同驱动报错名也不同:PyMongo 是 UnknownTransactionCommitResult,Node.js 驱动可能是 UnknownTransactionCommitResult 或缩写为 UnknownTrans。实操中要:
- 精确匹配异常类名或错误消息字符串(如
"UnknownTransactionCommitResult") - 采用指数退避:第 1 次失败后
sleep(0.1),第 2 次sleep(0.3),第 3 次直接抛出原始错误 - 重试上限建议设为 2~3 次——再多大概率是持久性故障,该告警而非死磕
readConcern: "snapshot" 就直接重试,导致两次读到不同余额,进而引发资损。这不是理论风险,是线上高频事故点。


















