一、什么是幻读
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。在当前读情况下是加锁来解决的,会阻塞其他事务的
当前读,从而避免幻读。然而可重复读并不能完全解决幻读,当一个事务里面使用快照读之后又使用当前读的话就还是
会出现幻读。