幻读主要发生在可重复读级别下,指同一范围查询结果集行数不一致;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)才会触发间隙锁
升级到串行化(Serializable)隔离级别
这是最彻底的方案,MySQL 会自动将所有普通 SELECT 隐式转为 SELECT ... LOCK IN SHARE MODE,使读操作也加锁,完全避免幻读。
但代价是并发性能大幅下降,写阻塞读、读也阻塞写,仅适用于对一致性要求极高、并发压力极低的场景(如金融核心批处理)。
应用层配合:使用唯一约束 + 重试机制
对于“检查后插入”类典型幻读场景(如防止重复下单),推荐更轻量的解法:
- 在业务字段上建立唯一索引(如订单号、用户+商品组合)
- 先尝试插入,捕获唯一键冲突错误(
ER_DUP_ENTRY) - 根据业务逻辑决定是返回失败还是重查后提示“已存在”
这种方式不依赖锁,性能好,且天然规避幻读引发的逻辑错乱,比纯靠数据库锁更健壮。
幻读不是 bug,是隔离级别与实现机制权衡的结果。InnoDB 在 RR 下已通过间隙锁覆盖了绝大多数写入型幻读,关键是要分清快照读和当前读,合理选择锁策略与应用设计。










