事务提交失败后必须立即调用session.end_session()并丢弃会话,否则会话状态损坏不可复用;重试需新建会话和事务,且所有操作须显式传入session参数以确保事务一致性。

事务提交失败时,commit_transaction() 抛出异常,但此时会话状态已损坏,不能继续复用该会话——这是最常被忽略的致命点。
事务失败后必须显式终止会话
MongoDB Python 驱动(pymongo >= 4.0)中,一旦 commit_transaction() 失败(比如因写冲突、超时或网络中断),当前 ClientSession 进入不可恢复状态。继续调用 end_session() 是安全的,但若尝试再次 start_transaction() 或执行操作,会抛出 InvalidOperation: Cannot use session outside of a transaction 或类似错误。
- 正确做法:在
except块里立即调用session.end_session(),然后丢弃该 session 实例 - 错误做法:捕获异常后试图重试
commit_transaction()或继续用同一 session 执行其他操作 - 注意:
end_session()不是可选的“清理建议”,而是强制要求;不调用可能导致连接泄漏或后续会话复用失败
重试逻辑必须新建会话和事务
自动重试事务不能复用旧 session,否则会触发状态不一致。典型场景如写冲突(TransientTransactionError)需指数退避重试,但每次都要从头开始。
- 用
with client.start_session() as session:确保每次重试都获得干净会话 - 事务体必须封装成可重入函数,避免闭包变量污染(例如不要在外部定义
doc_id后在事务内直接修改) - 示例关键片段:
for attempt in range(max_retries): try: with client.start_session() as session: with session.start_transaction(): collection.update_one(..., session=session) collection.insert_one(..., session=session) session.commit_transaction() break except (TransientTransactionError, ConnectionFailure) as e: if attempt == max_retries - 1: raise time.sleep(0.1 * (2 ** attempt))
避免在事务中调用非事务感知的操作
事务只保证 session 内操作的原子性,但以下行为会绕过事务控制,导致数据不一致:
- 在事务块内使用未传
session=参数的 CRUD 方法(如collection.find()而非collection.find(..., session=session))——这些操作走默认会话,不在事务上下文中 - 调用
client.admin.command()或其他管理命令(事务不支持跨数据库命令) - 在事务中发起 HTTP 请求、写本地文件、调用外部服务——这些无法回滚,且可能因阻塞延长事务时间,增加冲突概率
- 对分片集群,确保所有操作的目标集合位于同一分片(通过片键路由),否则事务会直接报错
CommandNotSupportedOnShardedCluster
真正麻烦的不是报错本身,而是事务失败后 session 对象仍“活着”却无法使用——它不会自动失效,也不会提示你该换新会话。只要没手动 end_session(),这个对象就一直占着资源,还可能在日志里静默吞掉后续错误。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











