retryWrites=true对网络抖动基本无效,因其仅在MongoDB 4.0+服务端+兼容驱动下,针对单文档写操作的NotPrimaryError或WriteConcernError等特定错误最多重试1次,不处理ConnectionTimeoutError、socket.timeout、事务错误及StaleConfigError等抖动场景。

PyMongo 本身不自动重试网络抖动引发的失败,必须靠显式配置 + 应用层逻辑兜底;盲目加 retryWrites=true 或手动循环 try/except 反而容易放大问题。
为什么 retryWrites=true 对网络抖动基本无效
这个参数只对「单文档写操作」在「NotPrimaryError」或「WriteConcernError」等特定错误下触发最多 1 次重试,且前提是服务端为 MongoDB 4.0+、驱动兼容、连接字符串里正确启用。它完全不处理以下抖动场景:
-
ConnectionTimeoutError:TCP 连不上,retryWrites根本没机会介入 -
socket.timeout:连上了但读响应卡住,属于 I/O 层超时,不在重试范围内 - 事务中任意步骤抛出
TransientTransactionError:它要求整个事务重试,不是单条写命令重试 - 分片集群下
StaleConfigError(code 13388):路由缓存过期,需幂等重试 + 退避,retryWrites完全不识别该错误码
分层设置超时参数才是防抖第一道防线
网络抖动本质是链路不稳定,先让连接行为“有边界”比事后重试更重要。三个超时必须显式设值,不能依赖默认:
-
connectTimeoutMS=5000:控制发起 TCP 连接的最大等待时间,设太小会误判慢网,太大拖慢故障发现 -
socketTimeoutMS=10000:单次请求读响应超时,避免慢查询长期占满连接池;注意它不影响游标遍历,游标要单独控batch_size -
serverSelectionTimeoutMS=10000:副本集选主节点总耗时,生产环境默认 30 秒太长,必须压到 10 秒内,否则抖动期间会卡死在选节点阶段
正确写法:mongodb://host:27017/?connectTimeoutMS=5000&socketTimeoutMS=10000&serverSelectionTimeoutMS=10000
对不同错误类型用不同重试策略
不能统一 catch Exception 然后无脑重试。必须按错误语义区分处理:
-
TransientTransactionError:用session.with_transaction()回调封装,所有操作显式传session,回调内禁止 HTTP 请求等外部 I/O -
StaleConfigError(code 13388):捕获后 sleep(200–500ms) 再throw,让with_transaction()自动重试;严禁新建ClientSession,否则可能路由到更旧的mongos -
ConnectionFailure或AutoReconnect:适合用tenacity做指数退避重试,但仅限初始化连接或低频管理操作,**绝不能用于高频写入循环** -
CursorNotFound:不是网络抖动,是游标超时被服务端清理,应减小batch_size或加no_cursor_timeout=True(并手动close())
PyMongo 里最容易被忽略的状态陷阱
很多重试失败,是因为 session 状态已失效但代码还在用:
-
ClientSession抛出StaleConfigError后,内部_server_session会被置为None,即使重试也不会恢复——不能靠session.has_ended判断,它只反映是否调用过end_session() - 事务中若发生
TransientTransactionError,当前session会进入 invalid 状态,后续任何操作都会抛InvalidSession,必须确保重试逻辑重建完整上下文 - 使用
with_transaction()时,回调函数必须是纯函数:不改外部变量、不发 HTTP、不读文件,否则重试会重复执行副作用
真正难的不是写重试代码,而是厘清每次失败到底属于哪一层——是 DNS 解析慢?TLS 握手卡住?还是 mongos 路由表没刷新?得先分清错误类型,再决定要不要重试、怎么重试、重试几次。

















