MySQL InnoDB 的默认隔离级别「可重复读」,可以很大程度上避免(不是彻底避免)幻读现象的发生,其解决方案是:

  • 针对快照读(普通 select 语句):通过 MVCC(多版本并发控制)方式解决幻读
  • 针对当前读select ... for update 等语句):通过 next-key lock(临键锁)方式解决幻读

快照读

通过 MVCC(多版本并发控制)方式解决幻读

开始事务后(执行 begin / start transaction),在执行第一个查询语句后,会创建一个 Read View,后续的查询语句利用这个 Read View,通过这个 Read View 就可以在 undo log 版本链找到事务开始时的数据,所以事务过程中每次查询的数据都是一样的,即使中途有其他事务插入了新纪录,是查询不出来这条数据的,所以就很好了避免幻读问题

当前读

MySQL 中除了普通查询是快照读,其他都是当前读,比如 updateinsertdeleteselect ... for update,这些语句执行前都会查询最新版本的数据,然后再做进一步的操作

这很好理解,假设要 update 一个记录,另一个事务已经 delete 这条记录并且提交事务了,这样不是会产生冲突吗,所以 update 时肯定要知道最新的数据

通过 next-key lock(临键锁)方式解决幻读

例如,表中有一个范围 id 为 (3, 5) 间隙锁,那么其他事务就无法插入 id = 4 的记录,这样就有效防止幻读现象的发生

幻读被完全解决了吗?

可重复读隔离级别下虽然很大程度上避免了幻读,但是还是没有能完全解决幻读

例如:

  • T1 时刻:事务 A 先执行「快照读语句」select * from t_test where id > 100 得到了 3 条记录
  • T2 时刻:事务 B 往插入一个 id = 200 的记录并提交
  • T3 时刻:事务 A 再执行「当前读语句」select * from t_test where id > 100 for update 就会得到 4 条记录,发生了幻读现象

==要避免这类特殊场景下发生幻读的现象的话,就是尽量在开启事务之后,马上执行 select ... for update 这类当前读的语句==,因为它会对记录加 next-key lock,从而阻塞其他事务插入新记录