事务必须使用primary读取偏好,因MongoDB服务端强制校验并拒绝非primary值,以保障原子性、因果一致性及跨分片事务的节点锁定要求;任何绕过尝试均失败,唯一合规方案是拆分为两个独立session:一个事务session(自动primary)和一个普通session(可设secondary)。

事务内强制使用 primary 读取偏好是硬性限制
不是配置没配对,也不是驱动不支持,而是 MongoDB 服务端直接拒绝非 primary 的读取偏好。只要你开启事务(session.startTransaction()),后续所有读操作(find()、countDocuments() 等)都会被服务器校验:如果请求携带了 readPreference=secondary 或任何非 primary 值,立刻返回错误 "Read preference in a transaction must be primary"。这个检查发生在 wire protocol 层,驱动层传参无效,mongosh 手动链式调用 .readPref('secondary') 也会当场报错。
primary 是事务原子性和因果一致性的基础设施要求
事务要求所有读写操作路由到同一个物理节点,且该节点必须是当前主节点(primary)。原因有二:
- oplog 写入和读取必须发生在同一节点,否则无法保证“读已提交”——从节点的数据可能滞后,你刚写入的文档在从节点上还没同步完,读就会丢失
- 多文档事务依赖单节点上的锁管理和 snapshot 隔离机制,跨节点无法协调事务视图,
snapshot读关注也只在primary上生效 - 跨分片事务更进一步:每个分片的
primary都必须在线且可通信,否则事务启动失败——这不是容错设计,而是架构前提
试图绕过会触发静默降级或运行时错误
常见误区包括:
- 在连接字符串里写
readPreference=secondary,再进事务——无效,事务上下文完全覆盖连接级设置 - 用 PyMongo 的
ReadPreference.SECONDARY初始化 client,然后在 session 里 startTransaction()——驱动会忽略该偏好,但如果你手动在事务中调collection.find().read_preference = SECONDARY,PyMongo 6+ 会抛InvalidOperation - 用 mongosh 连接后先执行
rs.slaveOk()——新版 mongosh 根本不认这命令,它只影响旧版 shell 的默认行为,与事务无关
Node.js Driver 和 C Driver 同样不提供 startTransaction({ readPreference: 'secondary' }) 接口,传了会被丢弃;PHP 驱动的 startTransaction() 参数列表里压根没有 readPreference 字段。
真正可行的读写分离方案是拆 session
业务需要“事务写 + 从节点读”,唯一合规做法是用两个独立 session:
- 一个带事务的 session(自动绑定
primary,只做写和强一致性读) - 另一个普通 session,显式设
readPreference: 'secondary'或'secondaryPreferred',专用于最终一致性读
注意:这两个 session 必须使用不同连接实例(或至少不同逻辑会话对象),不能共享同一个 session 对象再反复切换——事务一旦开启,该 session 的读取路径就锁定为 primary,直到 commitTransaction() 或 abortTransaction()。
最容易被忽略的是:事务的隔离性不靠参数控制,而靠节点一致性。哪怕你用 curl 直接发 raw command 绕过驱动,服务器仍会在 applyOps 阶段校验并拒绝非 primary 的读取偏好。别试 hack,拆 session 是唯一正解。











