MongoDB 的 readConcern: "majority" 对应读已提交,因其确保读取的数据已被大多数副本节点确认且不会回滚;该机制依赖 WiredTiger 的 oplog 时间戳与 majority-committed point 对齐,而非锁。

MongoDB 事务默认不是读已提交(Read Committed),必须显式配置 readConcern: "majority" 才能实现该隔离级别;readConcern: "snapshot" 是更强的隔离,但仅限多文档事务且要求副本集或分片集群启用选举机制(即非单节点部署)。
为什么 readConcern: "majority" 对应读已提交
MongoDB 的 "majority" 读关注保证你读到的数据已被大多数副本节点确认写入 —— 这意味着它不会回滚,等价于关系型数据库中“读已提交”的语义。注意:这不是靠锁实现的,而是依赖 WiredTiger 存储引擎的 oplog 时间戳与 majority-committed point 的对齐。
常见误用:readConcern: "local"(默认值)可能读到未确认、后续被回滚的写入;readConcern: "available" 在分片集群中甚至可能跳过未同步的 chunk 数据。
- 只在事务启动时设置,不能在
session.startTransaction()后单独为某次find()覆盖 - 驱动版本需 ≥ 4.2(Node.js driver ≥ 3.6),旧版忽略该选项不报错但无效
- 若副本集多数节点宕机,
"majority"读会阻塞或超时,而非降级
readConcern: "snapshot" 的真实约束条件
"snapshot" 提供事务内跨多个操作的一致快照视图(类似可重复读),但它不是开箱即用的:
- 仅支持多文档事务(
session.startTransaction({ readConcern: { level: "snapshot" } })),单个updateOne或find不生效 - 要求 MongoDB ≥ 4.0 + 副本集模式(或分片集群),单节点
mongod直接报错"Snapshot reads require a replica set" - 事务内所有读必须发生在同一 session,且不能混用
"majority"和"snapshot" - 性能开销明显:WiredTiger 需维护更长的 snapshot 生命周期,高并发长事务易触发
SnapshotTooOld
Node.js 驱动中正确配置事务读关注的写法
错误示范:collection.find(..., { readConcern: { level: "snapshot" } }) —— 这个选项会被忽略,因为读关注必须设在事务层级。
正确写法(以 Node.js driver 6.x 为例):
const session = client.startSession();
await session.withTransaction(async () => {
const coll = db.collection("orders");
// ✅ 正确:readConcern 在 startTransaction 中声明
await session.startTransaction({
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" }
});
const order = await coll.findOne({ _id: orderId }, { session });
await coll.updateOne({ _id: order.userId }, { $inc: { balance: -order.amount } }, { session });
});
关键点:
-
session必须透传给每个操作(findOne、updateOne等的{ session }选项) -
readConcern是startTransaction()的参数,不是 collection 方法的选项 - 如果省略
readConcern,事务将继承客户端默认值(通常是"local"),不是安全的读已提交
容易被忽略的兼容性陷阱
即使代码写对了,生产环境仍可能失败:
- 副本集未初始化完成(
rs.status().members[n].stateStr !== "PRIMARY")时,"snapshot"事务直接拒绝 - 分片集群中,若某个 shard 是
mongos路由到的mongod版本 "Snapshot read concern is not supported" - 使用
explain()查看执行计划时,readConcern不体现,需通过db.currentOp({ "secs_running": { "$gt": 5 } })观察事务状态中的readConcern字段
真正麻烦的是:这些限制往往只在压测或切流后暴露,而不是本地开发时报错。


















