MongoDB事务默认不重试,因事务是跨多条操作的逻辑单元,需应用层显式声明允许重试;仅单条写操作(如insertOne)由驱动自动重试,事务重试须满足:同一ClientSession且启用retryWrites: true。

MongoDB事务在主从切换时中断,不是事务本身不支持,而是客户端连接断开后未主动重试——ClientSideRetry 必须显式启用且依赖驱动版本与配置协同生效。
为什么默认不重试事务?
MongoDB 4.2+ 支持可重试写入(retryable writes),但事务(session.startTransaction())默认不包含在内。驱动仅对单条写操作(如 insertOne、updateOne)自动重试,而事务是跨多条操作的逻辑单元,需由应用层明确声明“允许重试”。
- 事务重试需满足:所有操作都在同一个
ClientSession中,且该 session 启用了retryWrites: true - 若使用旧版驱动(如 PyMongo retryWrites=true,事务也不会重试
- 事务中任意操作失败(如网络闪断、primary 切换导致连接中断),整个事务会抛出
TransientTransactionError,而非自动重试
如何正确启用 ClientSideRetry for Transactions
关键不在 MongoDB 服务端配置,而在客户端初始化和事务调用方式。必须同时满足三个条件:
- 连接字符串或客户端选项中设置
retryWrites=true(例如:mongodb://rs0.example.com:27017/?retryWrites=true) - 创建
ClientSession时传入{causalConsistency: false}(因果一致性与事务重试互斥) - 捕获
TransientTransactionError并手动重试整个事务逻辑,不能只重试某一步
示例(PyMongo):
from pymongo import MongoClient
from pymongo.errors import TransientTransactionError
<p>client = MongoClient("mongodb://localhost:27017/?retryWrites=true")
with client.start_session() as session:
while True:
try:
with session.start_transaction():
collection.update_one({"_id": 1}, {"$inc": {"count": 1}}, session=session)
collection.insert_one({"log": "tx-1"}, session=session)
break # 成功则退出循环
except TransientTransactionError:
continue # 驱动会自动处理连接重建,继续重试</p>
rs.stepDown() 演练时事务中断的典型现象
执行 rs.stepDown() 后,原 primary 主动降级,新 primary 选举完成前约 5–15 秒窗口期,正在执行的事务会收到 NotPrimaryNoSecondaryOk 或 InterruptedAtShutdown 错误,而非 TransientTransactionError —— 这说明事务未被识别为可重试上下文。
- 根本原因:事务 session 创建时未携带
retryWrites=true,或驱动版本过低 - 验证方法:检查
session.options.retry_writes是否为True(PyMongo)或session.client.options.retryWrites(Node.js) - 注意:
rs.reconfig()修改 priority 后触发的自动切换,行为一致;但强制 kill -9 主进程会导致更粗暴的连接中断,错误类型可能变为NetworkTimeout,此时不属于TransientTransactionError范围,需额外捕获
容易忽略的兼容性陷阱
事务重试能力高度依赖驱动与 MongoDB 版本匹配,不是“开了 retryWrites 就万事大吉”:
- PyMongo 3.12+ 才完整支持事务级重试;3.11 及以下仅支持单操作重试
- Node.js Driver 4.0+ 引入
withTransaction()辅助函数,自动封装重试逻辑;低于此版本需手写 while 循环 - MongoDB 服务器必须为 4.2+,且副本集协议版本 ≥ 1(可通过
rs.status().protocolVersion确认) - 若连接字符串含
maxPoolSize过小(如 1),重试时可能因连接池耗尽卡住,建议 ≥ 5
最常被跳过的一步:没验证 session 是否真绑定了重试能力——直接写事务却不检查 session.options,切到新 primary 后才发现事务静默失败。











