面试官:幻读是什么?可重复读一定能解决幻读吗?
在 Java 后端面试中,“幻读是什么”以及“可重复读(Repeatable Read, RR)隔离级别是否彻底解决了幻读”是高频考点。许多开发者误以为 InnoDB 默认的 RR 隔离级别能杜绝所有幻读问题,但在实际业务并发场景中,这种认知往往导致数据一致性问题。本文将深入剖析幻读的成因、RR 隔离级别下的读写机制差异,以及生产环境中如何正确规避幻读。
`` 是摘要与正文的分隔标记,请保留。幻读的本质与成因
幻读(Phantom Read)本质上是事务并发场景下的一种数据一致性问题。它指的是在同一个事务内,两次执行相同的范围查询时,第二次查询读到了第一次查询未读到的新增行,仿佛出现了“幻觉”一样。

幻读产生的根源在于事务的读写并发。不同隔离级别下,数据库读取数据的版本规则不同,最终呈现的结果也不一样。
可重复读(RR)隔离级别下的快照读机制
当在可重复读隔离级别下开启事务时,第一次执行普通快照读(Snapshot Read)时,InnoDB 会生成一个全局一致性视图(ReadView)。后续整个事务里的所有普通查询都复用这个视图,因此不会看到其他事务新提交的数据。
基于这一机制,很多资料声称 MySQL 的可重复读隔离级别已经解决了幻读问题。然而,这是一个非常常见的误区:可重复读的快照读只能保证普通查询看不到新插入的数据,但它完全不覆盖当前读(Current Read)的场景。
以下操作均属于当前读,每次执行都会读取最新的已提交数据,从而可能读到其他事务刚插入的新行:
- 使用
SELECT ... FOR UPDATE或SELECT ... LOCK IN SHARE MODE进行的加锁查询。 - 执行
UPDATE或DELETE写操作。 INSERT操作前的唯一性校验。

因此,线上经常遇到一种情况:明明处于可重复读隔离级别,事务里先查询是否存在再插入,却依然出现了重复数据或唯一键冲突。
为什么全用当前读也不能彻底解决幻读?
面试官通常会追问:“如果所有查询都加锁,全用当前读,总该没问题了吧?”答案是否定的。即使全用当前读,如果锁的范围不对,依然会出现幻读。主要存在以下三种风险场景:
1. 未命中索引导致锁升级
如果查询条件没有可用的索引,InnoDB 无法精准定位行,会直接升级为表级锁。虽然这种情况下不会出现幻读,但锁粒度极大,严重影响并发性能,生产环境基本无法接受。
2. 只有行锁,没有间隙锁
即使命中了普通索引,如果只锁住了已存在的记录行,而没有锁住记录之间的间隙(Gap),其他事务依然可以在间隙里插入新的数据。当你下次执行当前读时,就会读到这些新行,形成典型的幻读现象。
3. 快照读和当前读混用
如果在事务里先做了快照读(生成了固定视图),后面又执行了当前读操作,两次读取的数据版本不一致。同一个事务里两次查询结果不一样,本质上也是幻读的表现。

生产环境中的最佳实践
许多开发者以为 InnoDB 默认的可重复读就彻底解决了幻读,写业务代码时完全不做并发控制,这是非常危险的做法。
如果面试官继续深入,可以补充说明:标准 SQL 定义的可重复读隔离级别本身是不解决幻读的。 InnoDB 是通过 Next-Key Lock(临键锁)在当前读场景下抑制了幻读,但这是 MySQL 的特有实现,且只在正确加锁的场景下才生效。
因此,在生产环境中,不要默认可重复读就不会有幻读。针对涉及范围查询加写操作的并发场景,应采取以下措施:
- 加唯一约束:确保数据唯一性。
- 做并发控制:合理设计锁机制。
- 高并发场景:优先使用乐观锁或分布式锁来兜底。

核心总结
- 幻读本质:同一事务内,两次相同范围查询结果不一致,读到了其他事务新增的行。
- RR 的局限性:可重复读的快照读可以避免普通查询场景下的幻读,但不覆盖当前读场景。
- 当前读的依赖:当前读场景下依赖 Next-Key Lock 抑制幻读,若锁范围不全、索引失效或读写混用,依然会出现问题。
- 生产建议:不要依赖隔离级别解决幻读,要结合唯一约束、加锁及并发控制方案共同保证数据一致性。
如果在面试中能把这几个层次讲清楚,面试官基本就能判断出你是真正理解了 MySQL 事务隔离机制,而不仅仅是背诵了四个隔离级别的概念。