事务强制要求 ReadPreference.PRIMARY,因ACID需所有操作路由至同一副本集成员以保证原子性与一致性;驱动会校验并拒绝非PRIMARY偏好,或静默降级。

事务中必须用 ReadPreference.PRIMARY,其他任何模式都会报错或被忽略。
为什么事务强制要求 primary 读偏好
ACID 事务要求所有操作(包括读)路由到同一个副本集成员,否则无法保证原子性与一致性。MongoDB 驱动在开启事务时会自动校验 read_preference,若不是 PRIMARY,要么抛出 InvalidOperation 异常,要么静默降级为 PRIMARY(取决于驱动版本)。
- 事务内执行
find()、count()、aggregate()等读操作,都受此限制 - 即使你在连接字符串里写了
readPreference=secondary,事务启动后也会失效 - 试图在事务中手动调用
.read_preference(ReadPreference.SECONDARY)会直接失败
非事务场景下选错 read_preference 的典型表现
不涉及事务时,选错模式不会报错,但可能引发数据不一致或不可用问题:
-
secondary模式下读到明显过期数据(比如刚写入的订单状态仍是“待支付”,而主库已是“已发货”) -
nearest在跨区域部署中,因localThresholdMS默认值(15ms)太小,导致大量节点被排除,只剩 1–2 个可选,失去负载分散效果 -
primaryPreferred在主节点短暂失联期间切到从节点,后续主节点恢复后,应用未感知切换,仍持续读旧数据(无因果一致性会话时)
如何安全地在事务外使用 secondary 或 nearest
关键不是“能不能用”,而是“怎么用才不踩坑”:
- 必须确认业务能容忍最终一致性 —— 日志聚合、报表统计、用户行为分析这类场景可以,账户余额查询不行
- 显式传入
max_staleness_seconds(比如30),避免从严重滞后的节点读取;注意该参数对PRIMARY无效 - 搭配标签(tag)使用时,确保副本集节点已打标,且驱动中传的是
[{"region": "cn"}]这样的字典列表,不是字符串 - 不要在连接客户端时全局设
read_preference=ReadPreference.SECONDARY,除非整个应用所有读都不需要强一致
最容易被忽略的细节:read_concern 和 read_preference 是两回事
很多人以为设了 read_preference=ReadPreference.PRIMARY 就能读到最新数据,其实不然:
-
read_preference只决定“往哪个节点发请求”,不控制“能读到什么时间点的数据” - 要确保读到已提交数据,必须同时设置
read_concern={"level": "majority"}(服务端需开启enableMajorityReadConcern=true) - 事务内默认使用
read_concern="majority",但普通读操作不会自动继承,需显式传入


















