真实面经题目 · 原创解析

MySQL 如何防止幻读?

MySQL 防止幻读要分清快照读和当前读。InnoDB 在可重复读下通过 MVCC 让普通查询看到稳定快照,通过 next-key lock 和 gap lock 保护范围加锁查询,从而阻止其他事务在范围内插入新记录。

出现于:字节跳动 · 算法

60 秒回答模板

幻读指同一事务按相同条件再次查询时,出现了之前不存在、但符合条件的新行。MySQL InnoDB 的处理方式不是单一机制:普通 select 属于快照读,在可重复读隔离级别下使用事务级 ReadView,同一事务多次读取同一快照,所以看不到其他事务后来提交的新行;select for update、select lock in share mode、update、delete 这类当前读要读最新版本并加锁,为了防止范围内被插入新行,会依赖 next-key lock,也就是记录锁加间隙锁。next-key lock 锁住索引记录以及记录之间的间隙,其他事务无法在被保护的索引范围内插入符合条件的新行。需要注意,防幻读强依赖隔离级别、访问路径和索引条件;读已提交通常每条语句生成新快照,普通查询仍可能看到新提交记录。

考点 两套机制
难度 真实面经高频题
回答目标 讲清机制、边界和追问

深入解析

01

幻读的定义边界

幻读不是单行值被改了,而是满足同一谓词条件的结果集合发生了变化。例如事务第一次查询某个年龄区间没有记录,另一个事务插入一条符合条件的数据并提交,原事务再次查询看到这条新记录,这就是典型幻读。它和不可重复读的区别在于,不可重复读关注同一行内容变化,幻读关注范围查询中新增或消失的行。

02

快照读依赖 MVCC

普通 select 在 InnoDB 中通常走一致性非锁定读,也就是快照读。可重复读隔离级别下,事务首次快照读会创建 ReadView,后续快照读复用它,读取时沿 undo 版本链找对当前事务可见的版本。这样即使其他事务插入并提交了新记录,当前事务的快照读也不会把它纳入结果集,因此从读视角避免了幻读。

03

当前读需要范围锁

当前读必须读最新已提交版本,并且通常要为后续修改或一致性检查加锁。只靠 MVCC 快照不够,因为当前读如果不锁住范围,其他事务可以在两次当前读之间插入符合条件的新行。InnoDB 在可重复读下会对索引扫描范围加 next-key lock,既锁命中的索引记录,也锁相邻记录之间的间隙,阻止插入破坏范围稳定性。

04

索引决定锁的精度

next-key lock 是加在索引上的,访问路径越明确,锁范围越精确。如果条件命中唯一索引的等值查询,InnoDB 在部分场景可以退化成记录锁,因为不存在需要保护的插入间隙;如果范围条件没有合适索引,数据库可能扫描更多记录并锁更大的范围,甚至造成接近表级的并发影响。防幻读和索引设计关系很密切。

05

隔离级别的影响

读已提交下,普通快照读通常每条语句生成新的 ReadView,同一事务后续查询可能看到其他事务已经提交的新行,因此不能用事务级快照阻止幻读。可重复读是 InnoDB 常见默认选择,普通读靠 MVCC,当前读靠 next-key lock。串行化隔离级别更保守,会把更多读操作变成加锁读,但并发代价更高。

易错点

  • 只说 MVCC 能防幻读,却不区分快照读和当前读。
  • 把幻读解释成同一行字段值变化,混淆了不可重复读和幻读。
  • 认为 next-key lock 不依赖索引,忽略访问路径会决定锁范围。
  • 脱离隔离级别回答,误以为所有 MySQL 查询天然都不会出现幻读。

面试官追问

MVCC 已经让快照读不幻读,为什么还需要 next-key lock?

因为快照读只保证一致性读取,不阻止别人插入。当前读要读最新版本并可能继续修改,如果不锁范围,两次当前读之间仍可能多出新行。next-key lock 解决的是并发写入对范围稳定性的破坏。

间隙锁会锁住已经存在的记录吗?

纯 gap lock 只保护索引记录之间的插入间隙,不锁记录本身;next-key lock 是记录锁和间隙锁的组合,既锁记录又锁前面的间隙。面试中要把两者区分开。

唯一索引等值查询还会加 next-key lock 吗?

如果条件完整命中唯一索引并确定只会有一条记录,通常可以退化为记录锁,因为不存在同一键值再插入一条记录的空间。但范围查询、非唯一索引或未命中记录时仍可能涉及间隙保护。

读已提交为什么更容易出现幻读?

读已提交强调每条语句只读已提交数据,快照通常按语句创建。事务内第二次查询会基于新的可见性视角,其他事务已经提交的插入就可能进入结果集。