mysql默认repeatable read能防幻读,但依赖innodb的next-key lock(需索引+当前读),非标准sql定义;read committed仅靠mvcc快照不加间隙锁,无法防幻读;serializable虽彻底解决但并发性能极差。

MySQL 默认的 REPEATABLE READ 级别能防脏读、不可重复读,对幻读也有实际防护能力——但不是靠标准 SQL 定义,而是 InnoDB 用 Next-Key Lock 锁住索引间隙才压住的。想彻底规避幻读风险,不能只看隔离级别声明,得结合锁行为和查询是否走索引来判断。
为什么 READ COMMITTED 无法防止幻读
在 READ COMMITTED 下,每次 SELECT 都会新建一致性视图(MVCC 快照),但不锁定查询范围;事务 A 执行两次 SELECT * FROM orders WHERE amount > 100,事务 B 在中间 INSERT 一条新记录并提交,A 第二次查询就会多出一行。
- 幻读本质是「结果集行数变化」,不是单行值变,所以仅靠 MVCC 快照不够
-
READ COMMITTED不加间隙锁,B 的插入不会被阻塞,A 的两次查询看到的是不同快照 - 即使 B 的插入没走索引(比如全表扫描插入),A 也大概率看到新增行
REPEATABLE READ 怎么“意外”防住幻读
MySQL 的 REPEATABLE READ 和标准 SQL 的定义不完全一致:InnoDB 在可重复读下默认启用 Next-Key Lock(记录锁 + 间隙锁),只要查询条件能命中索引,就会锁住对应范围。
- 执行
SELECT * FROM orders WHERE amount > 100 FOR UPDATE,InnoDB 会锁住amount索引中所有大于 100 的记录,以及这些记录之间的间隙 - 事务 B 尝试插入
amount = 150的新订单时,会被阻塞,直到 A 提交或回滚 - 但如果查询没走索引(例如
WHERE status = 'pending'且status无索引),InnoDB 退化为全表扫描 + 行锁,间隙锁失效,幻读仍可能发生
SERIALIZABLE 是唯一标准解,但代价太大
SERIALIZABLE 会让所有普通 SELECT 自动隐式加上共享锁(LOCK IN SHARE MODE),写操作则加排他锁,强制事务串行执行。
- 它确实从语义上杜绝了幻读,但并发性能断崖式下跌——连只读查询都会互相等待
- 线上极少直接设全局
SERIALIZABLE,更常见的是在关键事务里显式加锁,比如SELECT ... FOR UPDATE配合REPEATABLE READ - 注意:如果应用层用了连接池,且事务未正确关闭,
SERIALIZABLE下的锁可能长期持有,拖垮整个库
真正容易被忽略的是:幻读是否发生,不只取决于隔离级别,更取决于查询是否走索引、是否显式加锁、以及事务是否在长事务中反复查询。哪怕设了 REPEATABLE READ,一个没索引的范围查询 + 长时间未提交的事务,照样会漏掉幻读。











