readconcern: "snapshot" 仅在副本集主节点、因果一致性会话、同一会话内读写、wiredtiger引擎且≥4.0版本时生效;分片集群中不支持,会报错或降级为"local"。

MongoDB 本身不提供标准 SQL 意义上的「快照隔离(Snapshot Isolation)」事务语义,snapshot 读关注(readConcern: "snapshot")仅在副本集的主节点或支持多文档事务的 WiredTiger 部署中有限可用,且**仅适用于因果一致会话下的特定场景**——它不能替代真正的 SI,也不能跨分片保证一致性。
什么时候 readConcern: "snapshot" 才真正生效?
该选项只在以下条件同时满足时才起作用:
- 运行在
replica set模式(非分片集群),且连接的是 primary - 使用
causalConsistency: true开启因果一致性会话 - 所有读写操作都在同一个会话(
ClientSession)中执行 - WiredTiger 存储引擎启用(默认),且 MongoDB 版本 ≥ 4.0(推荐 ≥ 4.2)
一旦用在分片集群、或未绑定会话、或读操作发往 secondary,readConcern: "snapshot" 会被静默降级为 "local" 或报错 InvalidOptions: snapshot read concern is not supported。
readConcern: "snapshot" 和 transaction 的关键区别
很多人误以为开启 snapshot 就能避免写偏斜(write skew),其实不能:
-
readConcern: "snapshot"只保证单次读操作看到某个一致的「过去快照」,但不阻塞并发写;后续写仍可能基于过期读结果提交 -
multi-document transaction(带readConcern: "snapshot")才真正提供快照级隔离:整个事务内所有读都基于同一快照,且事务提交前会做写冲突检测(通过_id或唯一索引字段) - 事务中的
readConcern: "snapshot"是隐式启用的,无需手动指定;手动指定反而可能被忽略
示例:事务内两次读取同一文档,即使中间有其他客户端修改并提交,这两次读看到的仍是事务开始时的值。
实际写法:如何安全地用快照保障一致性?
真要模拟 SI 行为,必须走事务路径,并注意以下细节:
- 启动会话时显式传入
{ causalConsistency: false }(事务不依赖因果一致性) - 事务选项中不要设
readConcern,让服务端自动选"snapshot" - 确保所有涉及数据校验的字段都有唯一索引(否则写冲突检测失效,可能产生写偏斜)
- 避免长事务:WiredTiger 快照保留时间受
storage.wiredTiger.engineConfig.snapshotCount和内存压力限制,超时后事务会因SnapshotUnavailable失败
简短示例(Node.js driver):
const session = client.startSession();
try {
await session.withTransaction(async () => {
const doc = await collection.findOne({ _id: 1 }, { session });
if (doc.balance > 100) {
await collection.updateOne({ _id: 1 }, { $inc: { balance: -50 } }, { session });
}
});
} finally {
await session.endSession();
}
为什么分片集群里基本用不了 snapshot 读关注?
分片环境下,readConcern: "snapshot" 要求所有 shard 同时提供同一逻辑时间点的数据快照,但 MongoDB 目前没有全局时钟同步机制。因此:
- 对分片集合执行带
"snapshot"的读,会直接报错ReadConcernNotSupportedOnShardedCluster - 跨分片事务虽支持,但其内部快照是 per-shard 的,不保证跨分片强一致性(例如无法防止 T1 读 shard A、T2 读 shard B 后各自提交造成的逻辑冲突)
- 若需跨分片一致性,只能靠应用层加分布式锁或业务逻辑规避,MongoDB 不提供原生 SI 支持
真正棘手的地方在于:错误往往不立刻暴露——降级后的 "local" 读可能看起来“正常”,直到出现脏读或不可重复读,而日志里连 warning 都没有。











