MongoDB默认readConcern为"local",仅读取节点最新数据而不保证多数确认,主节点宕机未同步时该数据会被回滚,导致事务中脏读;需显式设readConcern:"majority"并配对writeConcern:"majority"才可靠。

默认不防脏读,必须显式设 readConcern: "majority" 才能避免。
为什么默认事务仍可能读到回滚数据
MongoDB 事务启动时若未指定 readConcern,会继承连接默认值;4.0+ 版本连接默认是 "local"。而 readConcern: "local" 的语义是“读当前节点最新可用数据”,它不保证该数据已被多数节点确认——一旦主节点宕机且未同步完,这部分数据就会被回滚,造成脏读。
常见错误现象:session.startTransaction() 后直接 find(),没传 readConcern,结果在故障切换后发现读到的数据消失了。
- 单节点部署下
"local"看似安全,但副本集或分片集群中风险极高 - 即使用了事务,
readConcern不配对writeConcern也无效:事务提交时若用的是writeConcern: { w: 1 },那readConcern: "majority"就不提供保证 - 驱动版本低于 4.2(Node.js driver readConcern 设置,不报错也不生效
正确配置 readConcern: "majority" 的实操要点
这不是加个参数就完事,关键在时机和配套。
- 必须在
session.startTransaction()时通过options传入,例如:session.startTransaction({ readConcern: { level: "majority" } }) - 不能在事务内动态改:后续调用
collection.find().session(session)会自动沿用该设置,手动再传readConcern会触发InvalidOptions: readConcern cannot be changed within a transaction - 搭配的
writeConcern必须也是{ w: "majority" },否则读不到自己刚写的值——这是最常被忽略的因果链 - 副本集需启用
enableMajorityReadConcern: true(4.4+ 默认开启,但旧部署可能关着)
为什么设了 "majority" 还有时读不到新写入
这不是配置失败,而是 MongoDB 对“已提交”的定义和应用层直觉不一致。
事务 commitTransaction() 返回成功 ≠ 数据已推进到 majority committed point(MCP)。WiredTiger 先落盘,再异步刷 oplog,最后各节点更新 MCP——这中间有毫秒级延迟。
- 刚
commit就立刻用另一个 session 去find(),即使都用"majority",也可能读到旧值 - 分片集群中更明显:config server 和 shard 的 MCP 不同步,跨分片事务的读取延迟更高
- 解决办法不是降级,而是用同一 session 做“读己写”:所有读写都在同一个
session内发起,MongoDB 会自动做因果一致性对齐
容易被忽略的边界情况
生产环境出问题,往往卡在这些非主路径上。
- PSA 架构(Primary-Secondary-Arbiters)下,若 Secondary 宕机,
"majority"实际退化为w: 2(主 + 仲裁),但仲裁节点不存数据——此时"majority"读可能阻塞或超时,而非降级 - 使用
readPreference: "primaryPreferred"时,如果事务中读操作落到刚升主的节点,而它还没同步完旧主的事务状态,存在极短窗口期的瞬时脏读(概率低,但非零) - 非事务性读(比如没进
withTransaction(),也没显式startTransaction())完全不受readConcern事务约束,哪怕连接默认设了"majority",这类读照样可能脏读
真正起作用的从来不是孤立的配置项,而是 session 生命周期内 readConcern、writeConcern、readPreference 三者的协同边界——漏掉任意一环,隔离性就塌一角。


















