一、什么是幻读
1.我们先来回顾一下MySQL中事务隔离级别
- READ UNCOMMITTED :未提交读。
- READ COMMITTED :已提交读。
- REPEATABLE READ :可重复读。
- SERIALIZABLE :可串行化。
2.针对不同的隔离级别,并发事务可以发生不同严重程度的问题
- READ UNCOMMITTED 隔离级别下,可能发生脏读、不可重复读和幻读问题。
- READ COMMITTED 隔离级别下,可能发生不可重复读和幻读问题,但是不会发生脏读问题。
- REPEATABLE READ 隔离级别下,可能发生幻读问题,但是不会发生脏读和不可重复读的问题。
- SERIALIZABLE 隔离级别下,各种问题都不会发生。
3.MySQL的默认隔离级别是REPEATABLE READ,可能会产生的问题是幻读,也就是我们本次要讲内容。
首先来看看 MySQL 文档是怎么定义幻读(Phantom Read)的:
当同一个查询在不同的时间产生不同的结果集时,事务中就会出现所谓的幻象问题。例如,如果 SELECT 执行了两次,但第二次返回了第一次没有返回的行,则该行是“幻像”行。
如图所示
二、可重复读是如何避免幻读的
快照读情况下
在可重复读隔离级别下是通过MVCC来避免幻读的,具体的实现方式在事务开启后的第一条select语句生成一张Read View(数据库系统当前的一个快照),之后的每一次快照读都会读取这个Read View。
即在第②时刻生成一张Read View,所以在第⑤时刻时读取到数据和第②时刻相同,避免了幻读。
当前读情况下
当前读:像select lock in share mode(共享锁), select for update ; update, insert ,delete这些操作都是一种当前读,读取的是记录的最新版本。
在当前读情况下是通过next-key lock来避免幻读的,即加锁阻塞其他事务的当前读。
事务A在第②时刻执行了select for update当前读,会对id=1和2加记录锁,以及(2,+∞)这个区间加间隙锁,两个都是排它锁,会阻塞其他事务的当前读,所以在第③时刻事务B更新时阻塞了,从而避免了当前读情况下的幻读。
三、可重复读完全解决幻读了吗?
MySQL的默认隔离级别可重复能避免大部分情况下的幻读,但是在一些特殊场景下还是无法完全解决幻读。例如
在第②时刻使用的是快照读,此时生成了Read View查询出来的数据是张三、李四,事务B在第③时刻插入了一条id为3的王二,因为事务A并没有对数据加锁,所以事务B可以正常插入。但当第⑤时刻事务A查询时却查出来了事务B插入的数据,产生了幻读。是因为使用的是当前读,不会读取Read View,是读取数据当前最新的数据,所以读出了事务B插入的数据。
四、总结
MySQL的默认隔离级别可重复读很大程度上解决了幻读问题。在快照读情况下是通过MVCC解决的,在第一次执行查询语句时生成一张Read View,后续每次快照读都是读这张Read View。在当前读情况下是加锁来解决的,会阻塞其他事务的当前读,从而避免幻读。然而可重复读并不能完全解决幻读,当一个事务里面使用快照读之后又使用当前读的话就还是会出现幻读。