事务中读不到刚写入的数据,大概率是未设置 readConcern: "snapshot";该级别才能保证事务内读写一致和“写后立即可读”,而默认的 "local" 不支持,且仅在副本集或分片集群上可用。

事务中读不到刚写入的数据,大概率是 readConcern 没设对
在 MongoDB 多文档事务里,即使你用 session.withTransaction() 包裹了写和读操作,也**不会自动保证“写后立即可读”**。默认的 readConcern 是 "local",它只保证读到已写入当前节点的数据,但不保证该数据对当前事务可见——尤其在事务未提交前,其他操作(包括同一事务内的后续读)可能看不到中间状态。
真正起作用的是 readConcern: "snapshot",它让整个事务看到一个时间点一致的快照视图,且这个快照包含本事务已写入但尚未提交的数据(即支持 Read-Your-Writes 语义)。
- 必须显式传入
readConcern: { level: "snapshot" }到事务选项中,仅靠writeConcern或readPreference无效 -
"snapshot"仅在副本集(含主节点)或分片集群上可用;单节点模式下不支持,会报错Read concern level 'snapshot' is not supported - 事务开启时若未指定
readConcern,MongoDB 不会 fallback 到"snapshot",而是坚持用"local"
readConcern: "snapshot" 的实际写法和常见漏点
错误示范:只设了 writeConcern,没管 readConcern
await session.withTransaction(async () => {
await collection.insertOne({ x: 1 }, { writeConcern: { w: "majority" } });
// ❌ 这里读不到 { x: 1 },因为 readConcern 还是默认的 "local"
const doc = await collection.findOne({ x: 1 });
});
正确写法:在 withTransaction 的 options 参数里明确声明
await session.withTransaction(async () => {
await collection.insertOne({ x: 1 });
const doc = await collection.findOne({ x: 1 }); // ✅ 现在能读到了
}, {
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" }
});
- 注意:这个
readConcern是传给withTransaction()的第二个参数,不是插在findOne()或insertOne()的 options 里 - 如果用了 Mongoose,需确保版本 ≥ 6.0,并在连接时启用事务支持:
mongoose.set('useFindAndModify', false)(虽非直接相关,但旧版易掩盖问题) - 事务内所有读操作(
find、findOne、countDocuments等)都受此readConcern控制,无需每个都重复设
为什么不用 readConcern: "majority"?
"majority" 确保读到已写入大多数节点的数据,但它**不提供事务内一致性快照**。在事务中,它仍可能读不到本事务刚写但未提交的数据——因为那些数据还没被多数节点确认,也不在 majority 快照里。
-
"majority"适合跨事务读取(比如外部服务查刚提交的订单),但不适合“本事务内写后读” -
"snapshot"是唯一能保证事务内读写一致性的级别;它的代价是 slightly higher latency 和额外 snapshot 管理开销,但在强一致性场景下不可替代 - 如果你在事务里读不到数据,又确认了集合名、条件、类型都没错,那几乎可以断定是
readConcern缺失或设错
容易被忽略的兼容性细节
Snapshot read concern 要求底层存储引擎支持,而 MongoDB 默认的 WiredTiger 引擎没问题,但以下情况会直接失败:
- 连接的是单节点 mongod 实例(非副本集)→ 报错
Snapshot reads are not supported on standalone servers - 副本集初始化未完成(
rs.status().members[n].stateStr !== "PRIMARY")→ 事务无法启动 - 使用了
readPreference: "secondary"→"snapshot"不支持从 secondary 读,会静默降级或报错,必须保持"primary" - 事务持续时间超过
transactionLifetimeLimitSeconds(默认 60 秒)→ 自动中止,后续读全部失效
最常踩的坑,是本地开发用单节点跑事务还硬配 "snapshot",结果连事务都启不来。上线前务必确认部署拓扑是否满足要求。


















