可重复读的幻读问题
可重复读是MySQL数据库的默认隔离级别。可重复读指的是,事务1开启事务后,读取了数据库的某一条数据,然后事务2对该数据进行了修改并提交,这时事务1读取到的还是修改前的数据。
用两个dos窗口模拟事务1和事务2,在事务1开启事务后,查看test表中有数据如下
然后事务2也开启事务,并对test表中进行修改,将id为1的名字修改为’刘诗诗‘,并提交事务
然后事务1再次查看test表中的数据
通过以上测试可知,当事务隔离级别为可重复读是,避免了不可重复读的问题,那么避免了幻读问题吗?
幻读是指:在事务执行过程中,前后两次相同的查询得到的结果集不一致。
请看以下测试:
在刚才的例子中,我们将事务1commit,然后开启事务并查看test表数据
在事务2中开启事务,插入一条‘刘亦菲’的数据,并commit
然后事务1执行select * from test for update;
查看表数据
发现读到了刚才插入的数据‘刘亦菲’。
通过以上可知:当事务隔离级别为可重复读时,MySQL在某些情况会避免幻读,但并没有完全避免幻读问题。
出现幻读原因
这里有两个概念,快照读和当前读
快照读
快照读,顾名思义,快照指的是固定的某个时刻的数据,就好比现实世界中的拍照一样,把某个时刻记录下来。
普通的select语句都是采用的快照读,也就是说,当事务隔离级别是可重复读,并且执行select语句是一个普通的select语句时,都会采用快照读的方式读取数据
当前读
当前读,顾名思义,每一次都读取最新的数据。当前读包括:update、delete、insert、select...for update。因为增删改的时候要基于最新的数据进行增删改。
出现幻读的原因
出现幻读的原因就是第一次使用快照读,后面使用了当前读,所以出现幻读现象
出现幻读的两种情况
第一种幻读场景
事务1与事务2,在事务1中第一次查询使用快照读,在事务2中对数据进行修改并提交,然后在事务2中第二次查询使用当前读,则会产生幻读现象。本文一开始的幻读演示就是属于这种情况,在这不重复演示了。
第二种幻读场景
事务1与事务2,在事务1中第一次查询使用快照读,在事务2中对数据进行修改并提交,然后在事务1中对数据进行修改,最后在事务1中再次使用快照读,则会发生幻读现象。
事务1:
事务2:
事务1:
通过测试我们可以知道:事务1出现了幻读,但是并没有读到事务2插入的数据‘贾玲’,但是读到了事务1插入的数据‘迪丽热巴’,这是因为insert into test values(6,'迪丽热巴')
这条语句触发了当前读,而最后的select * from test
实际上是事务1第一次查询的快照读与事务1插入语句的当前读的结合。
解决幻读问题的方法及原理
串行化(serializable)
串行化是一种事务的隔离级别,它是最高的隔离级别,避免了所有问题,这种隔离级别会导致事务排队处理,不支持并发,效率低,所以在实际应用中并不推荐。
推荐的方法是两次查询都用快照读或当前读
针对快照读
针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好的避免了幻读问题。
MVCC(多版本并发控制),实现的方式是,开启事务后,在执行第一个查询语句后,会创建一个Read View,后续的查询语句利用这个 Read View,通过这个 Read View 就可以在 undo log 版本链找到事务开始时的数据,所以事务过程中每次查询的数据都是一样的,即使中途有其他事务插入了新纪录,是查询不出来这条数据的,所以就很好的避免了幻读问题。
第一次查询
第二次查询
针对当前读
针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select ... for update 语句的时候,会对查询范围内的数据加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好的避免了幻读问题。注意:锁的不是整张表,是select的数据及其间隙
select...for update原理是:对查询范围内的数据进行加锁,不允许其它事务对这个范围内的数据进行增删改。也就是说这个select语句范围内的数据是不允许并发的,只能排队执行,从而避免幻读问题。
select...for update加的锁叫做:next-key lock。我们可以称其为:间隙锁 + 记录锁。间隙锁用来保证在锁定的范围内不允许insert操作。记录锁用来保证在锁定的范围内不允许delete和update操作。
事务1
事务2
事务1
可以发现,事务2试图修改id=3的数据,但是不能成功,这是因为事务1在执行select * from test where id between 2 and 4 for update;
后,id在[2,4]区间的所有数据行被锁定,事务2不能对该区间的数据进行修改,这一过程是通过记录锁来实现的。
总结可重复读的幻读问题
-
产生原因:第一次使用快照读,后面使用了当前读。
-
解决办法:
-
针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读。
-
针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读。
-