幻读主要发生在可重复读级别下,指同一范围查询结果集行数不一致;InnoDB通过MVCC+间隙锁解决当前读幻读,快照读的“幻影”属正常行为;应用层可用唯一索引+重试机制高效规避。
MySQL 中的幻读问题,主要出现在可重复读(Repeatable Read)隔离级别下,当一个事务中前后两次执行相同的范围查询,结果集的行数不一致(比如中间被其他事务插入了新记录),就发生了幻读。
幻读不是读到了“不存在”的数据,而是读到了“新插入”的数据。它和不可重复读的区别在于:不可重复读是同一行数据被修改,幻读是新行被插入或删除导致结果集变化。
在 MySQL 的 InnoDB 引擎中,RR 级别通过多版本并发控制(MVCC)+ 间隙锁(Gap Lock)来解决大部分幻读问题,但仅限于当前读(如 SELECT ... FOR UPDATE、SELECT ... LOCK IN SHARE MODE、UPDATE、DELETE)。普通的快照读(普通 SELECT)仍可能看到“幻影”,但这属于 MVCC 正常行为,不视为事务异常。
当需要严格避免幻读(例如做范围校验后再插入),应使用当前读并依赖间隙锁锁定范围:
SELECT * FROM t WHERE id BETWEEN 10 AND 20 FOR
UPDATE;,InnoDB 不仅锁住已有记录,还会锁住 (10,20) 这个间隙,阻止其他事务在此区间插入新行WHERE id = 15)只加记录锁,不加间隙锁;而范围查询(>、、BETWEEN)才会触发间隙锁
这是最彻底的方案,MySQL 会自动将所有普通 SELECT 隐式转为 SELECT ... LOCK IN SHARE MODE,使读操作也加锁,完全避免幻读。
但代价是并发性能大幅下降,写阻塞读、读也阻塞写,仅适用于对一致性要求极高、并发压力极低的场景(如金融核心批处理)。
对于“检查后插入”类典型幻读场景(如防止重复下单),推荐更轻量的解法:
ER_DUP_ENTRY)这种方式不依赖锁,性能好,且天然规避幻读引发的逻辑错乱,比纯靠数据库锁更健壮。
幻读不是 bug,是隔离级别与实现机制权衡的结果。InnoDB 在 RR 下已通过间隙锁覆盖了绝大多数写入型幻读,关键是要分清快照读和当前读,合理选择锁策略与应用设计。