事务提交失败后必须新建clientsession重试整个事务,而非复用原会话或仅重试commit;需在新会话中重新执行全部读写操作并采用带退避的循环。

事务提交失败后不能重试 commitTransaction()
事务提交失败(比如写冲突、网络中断、主节点切换)后,ClientSession 状态会立即变为不可用——再调用 commitTransaction() 或 abortTransaction() 都会抛 InvalidSessionException。这不是你重试逻辑写错了,而是驱动强制终止了该会话。
常见错误是捕获到 commitTransaction() 异常后,在原 session 上继续调用 abortTransaction() 或再次 commitTransaction(),结果直接报错。
- 必须放弃当前
ClientSession,每次重试都新建一个 - 整个事务体(含所有读、写操作)要重新执行,不是只重试 commit 步骤
- 重试前不要复用上一轮读到的数据缓存,必须在新 session 中重新
find()或findOneAndUpdate()
重试循环必须包裹整个事务逻辑
把事务操作封装成一个接受 ClientSession 的函数,外层用带退避的循环控制重试。PyMongo 的 with_transaction() 只重试提交阶段,且无退避策略,不适合生产环境。
示例结构(Java):
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
for (int attempt = 0; attempt
-
transferMoney()内部所有insertOne()、updateOne()等都必须显式传session参数 -
try-with-resources确保 session 关闭,避免连接池耗尽 - 退避时间建议从 100ms 起,最多 3–5 次,避免雪崩
事务体必须幂等,且读操作需 snapshot 一致性
重试时数据可能已被其他事务修改,所以事务内读操作不能依赖“之前读过”,也不能在事务外缓存结果复用。同时,为防止重试过程中读到不一致快照,应显式设置 readConcern。
- 用
upsert替代insertOne(),避免重复插入 - 用
$setOnInsert控制初始值,保证多次执行结果相同 - 读操作如
find(session, filter)建议配合ReadConcern.SNAPSHOT(需 MongoDB 4.0+、副本集) - 避免在事务中调用外部 HTTP 接口,否则重试会放大不确定性
retryWrites 和 retryReads 对事务重试无影响
retryWrites=true(默认开启)只对**单文档写入**生效,比如 insertOne()、updateOne();它对事务内的写入、commitTransaction()、abortTransaction() 不起作用。
同理,retryReads=true(MongoDB 6.0+ 默认)仅重试独立读操作(如 find()),但**事务内读操作不会被自动重试**——它们属于事务上下文,由你自己的重试循环统一控制。
- 别指望靠配置开关解决事务失败问题,必须手写重试逻辑
- 事务内所有操作都绑定同一个
ClientSession,而 session 是线程绑定的,不可跨线程共享 - Spring 的
@Transactional对 MongoDB 原生事务无效,二者机制不兼容
事务重试真正的复杂点不在代码怎么写,而在于判断哪些操作可以安全重试、哪些必须人工介入(比如涉及资金扣减但下游已发货)。幂等设计和 snapshot 读一致性是绕不开的硬门槛。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










