幻读是事务中两次范围查询结果行数不一致,因其他事务插入/删除导致;不可重复读是同一行数据值被修改。MySQL的REPEATABLE READ通过间隙锁抑制幻读但未消除其语义,SERIALIZABLE虽彻底解决却牺牲并发,合理索引与SQL优化更实用。
幻读到底是什么,和不可重复读有什么区别
幻读不是“看到幻觉”,而是指事务中两次 SELECT 同一范围数据,第二次多出(或少掉)了新插入(或删除)的行——这些行在第一次查询时并不存在,但被别的事务提交了。关键在于:它针对的是「范围查询」,比如 WHERE age > 25 或 SELECT * FROM user WHERE status = 1,而不是单行主键查询。
不可重复读是查同一行,值变了;幻读是查一个范围,行数变了。很多人混淆,是因为 MySQL 默认的 REPEATABLE READ 隔离级别下,InnoDB 用间隙锁(Gap Lock)+ 行锁模拟出“无幻读”效果,但这只是表象——它没真正消除幻读语义,只是把并发写冲突提前拦住了。
MySQL 的 REPEATABLE READ 真的能防止幻读吗
在 InnoDB 中,REPEATABLE READ 下普通 SELECT 是快照读(Snapshot Read),不加锁,靠 MVCC 版本链避免脏读和不可重复读;但只要涉及当前读(如 SELECT ... FOR UPDATE、UPDATE、DELETE),InnoDB 就会加临键锁(Next-Key Lock),即「行锁 + 间隙锁」,从而封锁索引区间,阻止其他事务在该范围内插入新记录。
这意味着:
- 如果你只用纯
SELECT(无锁读),幻读不会发生——因为看到的是事务开始时的一致快照 - 但如果你执行
SELECT ... FOR UPDATE查范围,然后另一个事务尝试INSERT进这个间隙,会被阻塞或报死锁——这不是“防止幻读”,而是“用锁压制了幻读发生的条件” - 一旦你没走索引(比如
WHERE name LIKE '%abc%'),间隙锁退化为全表锁,性能雪崩
想真正规避幻读,必须用 SERIALIZABLE 吗
不用。MySQL 的 SERIALIZABLE 确实会对所有 SELECT 自动加上 LOCK IN SHARE MODE,变成串行执行,彻底杜绝幻读,但代价极高:并发能力几乎归零,且容易触发大量锁等待。
更务实的做法是: