事务中必须用 readConcern: "snapshot",因为它能保证多文档事务的可串行化隔离级别,绑定事务开始时的集群一致快照,避免脏读、不可重复读及原子性破坏;其他读关注如"local"或"majority"无法提供该保障。

为什么事务里必须用 readConcern: "snapshot"
因为只有 "snapshot" 能保证多文档事务的可串行化(serializable)隔离级别。其他读关注如 "local" 或 "majority" 都可能读到中间态数据,导致脏读、不可重复读,甚至违反事务原子性语义。
比如你在事务中先查订单状态,再根据结果更新库存——若用 "majority",可能刚查完订单就被另一个事务改掉,而你的后续更新仍基于过期快照执行,最终库存对不上。
-
"snapshot"会绑定事务开始时的集群快照时间点,整个事务所有读操作都从这个一致视图出 - 它要求写操作必须用
writeConcern: "majority"提交,否则快照无法被多数节点确认,读取会失败 - 在分片集群中,
"snapshot"是唯一能跨分片提供一致读取的读关注级别
readConcern: "snapshot" 在事务外也能用,但要注意什么
MongoDB 5.0+ 确实允许非事务查询也用 "snapshot",比如报表导出或长时分析任务,但它不是“随便开”的开关。
常见误用是把它当成“实时一致性”方案——其实它读的是已提交的旧快照,延迟取决于写入频率和 WiredTiger 快照保留策略。
- 只支持
find、aggregate(未分片集合)、distinct;count、mapReduce、explain等命令不支持 - 不能在固定大小集合(capped collection)上使用,会直接报错
SnapshotUnavailable - 如果查询期间有集合被
drop或rename,且系统未能及时检测,可能返回空结果或错误数据(不是崩溃,而是逻辑错)
事务里设 readConcern 的位置很关键
你不能在每个 find() 或 updateOne() 单独加 readConcern,必须在开启事务时一次性声明。
驱动层(如 Node.js 的 mongodb driver)要求通过 startTransaction({ readConcern: { level: "snapshot" } }) 设置,而不是传进具体操作选项里。
- 一旦事务启动,所有内部读操作自动继承该
readConcern,改不了 - 显式在单个操作里再传
readConcern会被忽略(driver 会 warn,但不报错) - 如果没配,事务默认用
"local",此时即使你写了writeConcern: "majority",也无法获得快照隔离
容易被忽略的兼容性硬限制
不是所有部署都能直接用 "snapshot" 事务——它依赖底层机制,踩坑往往发生在环境配置上。
- 副本集必须启用
enableMajorityReadConcern: true(5.0+ 默认开启,但升级旧集群时可能仍为 false) - 分片集群要求所有分片和 config server 都运行 MongoDB 4.2+,且
sharding.version≥ 5 - 不能在
admin、config、local数据库里开启事务,哪怕只是读也会被拒绝 - 事务内创建集合仅限于已有分片键的集合;跨分片隐式建表直接失败,错误是
CannotImplicitlyCreateCollection
真正麻烦的不是语法写错,而是快照不可用时 MongoDB 不立刻报错,而是降级到 "local" 并静默执行——得靠日志里 transaction was not using snapshot read concern 这类提示去反查。


















