可重复读的幻读问题

可重复读的幻读问题

可重复读是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(记录锁+间隙锁)方式解决了幻读。

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java老狗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值