MySQL InnoDB 的默认隔离级别「可重复读」,可以很大程度上避免(不是彻底避免)幻读现象的发生,其解决方案是:
- 针对快照读(普通
select
语句):通过 MVCC(多版本并发控制)方式解决幻读 - 针对当前读(
select ... for update
等语句):通过 next-key lock(临键锁)方式解决幻读
快照读
通过 MVCC(多版本并发控制)方式解决幻读
开始事务后(执行 begin / start transaction
),在执行第一个查询语句后,会创建一个 Read View,后续的查询语句利用这个 Read View,通过这个 Read View 就可以在 undo log 版本链找到事务开始时的数据,所以事务过程中每次查询的数据都是一样的,即使中途有其他事务插入了新纪录,是查询不出来这条数据的,所以就很好了避免幻读问题
当前读
MySQL 中除了普通查询是快照读,其他都是当前读,比如 update
、insert
、delete
、select ... 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,从而阻塞其他事务插入新记录