MongoDB事务隔离由readConcern控制,默认readConcern:"majority"防脏读;readConcern:"snapshot"支持快照隔离,可防脏读、不可重复读及部分幻读,但幻读仍需业务层配合唯一索引或upsert等机制防范。

MongoDB 不提供 SQL 标准定义的 READ UNCOMMITTED、REPEATABLE READ 等隔离级别命名接口,它的事务一致性由 readConcern 控制,且默认就是防脏读的——只要用对了 readConcern,脏读基本不会发生;但幻读在某些场景下仍需业务层配合防范。
readConcern 是 MongoDB 事务隔离的实际开关
MongoDB 的“隔离级别”不是靠 SET TRANSACTION ISOLATION LEVEL 这类语句设置的,而是通过操作时显式指定 readConcern 参数来决定读取哪一版本的数据。它不叫“隔离级别”,但效果等价:
-
readConcern: "local":可能读到未提交/可回滚的数据,类比 SQL 的“读未提交”,生产环境禁用 -
readConcern: "majority":只读已写入大多数节点的数据,避免脏读,是多文档事务的默认值(4.0+) -
readConcern: "snapshot":启用快照隔离(Snapshot Isolation),跨分片事务也保证时间线一致,能防住脏读、不可重复读、部分幻读和写倾斜
注意:readConcern 必须在事务内生效(即在 withTransaction() 或显式 startTransaction() 中传入),单独对集合做 find() 不受保护。
为什么说 MongoDB 默认就防脏读?
因为 MongoDB 多文档事务从 4.0 开始强制要求使用 readConcern: "majority"(除非显式覆盖为 "local")。这意味着:
- 事务 A 写入后,必须被多数副本集节点确认,事务 B 才能通过
readConcern: "majority"读到 - 如果事务 A 最终回滚,那些“已被多数节点确认但尚未 commit”的变更会被回滚掉,B 永远看不到它们
- 所以只要你不手动设成
"local",就不会有脏读
常见错误:在事务外用 collection.find().readConcern("majority") ——这无效,readConcern 只在事务上下文或带 session 的查询中起作用。
幻读在 MongoDB 中怎么出现又怎么压?
幻读本质是“两次 find() 返回不同数量的文档”,MongoDB 的 snapshot 级别能防住它,但有前提:
- 仅限单分片集群或启用了
readConcern: "snapshot"的跨分片事务 - 普通
"majority"读关注在多分片场景下,可能读到不同分片在不同时间点的状态,导致“看似新增”的幻影文档 - 即使用了
"snapshot",如果业务逻辑依赖“查无结果 → 插入”,而没加唯一索引或条件判断,仍可能因并发插入产生逻辑幻读(比如双写优惠券)
实操建议:
- 对关键业务字段建
unique索引(如订单号、用户手机号),靠唯一约束兜底 - 用
findOneAndUpdate带upsert: true替代“先查再插”流程 - 跨分片事务务必显式传
readConcern: "snapshot",且确认分片集群已启用featureCompatibilityVersion: "6.0"或更高
容易被忽略的边界:非事务性读 + 主节点切换
即便你所有事务都用了 "snapshot",如果应用层混用了非事务性读(比如后台报表直接 db.collection.find()),且 readPreference 设为 "primaryPreferred",主节点故障切换瞬间可能出现瞬时脏读——新主还没同步完旧主的事务状态,就响应了读请求。这不是 bug,是最终一致性模型下的正常窗口期。解决办法只有两个:要么统一走事务会话,要么接受这个极短(通常

















