【MySQL事物进阶】MySQL可重复读隔离级别完全解决幻读问题了吗

本文为博主对《小林coding》的学习笔记,仅供参考,具体内容请参见文章转载

目录

什么是幻读

快照读(普通select语句)是如何避免幻读的

 当前读是如何避免幻读的

      当前读的应用场景 

       间隙锁

在可重复读的隔离级别下面幻读真的被完美解决了吗

  第一个场景:同一个事物,两次查询之间对于其他事物新增的数据进行了修改

  第二个场景,使用范围查询的时候,不用select...for update来查询


在这一篇文章当中,提到了在可重复读的级别下面,是如何解决幻读问题的MySQL事物以及事物的四大特性,隔离级别的简单介绍_mysql中指定事物的特征的语句是_革凡成圣211的博客-CSDN博客https://blog.csdn.net/weixin_56738054/article/details/127857770?spm=1001.2014.3001.5502

     针对快照读(普通select语句)是通过MVCC的方式解决幻读问题的。

     针对当前读(select...for update)是通过间隙锁来解决的


什么是幻读

       幻读:在同一个事物当中,查询在不同的时间,但是是相同的查询,第二次查询的结果集和第一次查询的结果集不一样,这种现象,就被称为幻读。

select*from t_text where id>100
时间轴事件查询结果条数
t1查询事物开启
t2select*from t_text where id>1005
t3select*from t_text where id>1006
t4commit

快照读(普通select语句)是如何避免幻读的

       实现的方式是,开启事物之后,在第一次查询之后,会创建一个Read View。

       后续的查询,会通过这个Read View寻找到undo log版本链找到事物刚刚开始时候的数据。因此,事物就可以通过这个Read View寻找到事物刚刚开始时候的数据。因此,在这整个事物的过程当中,读取到的数据都是事物刚刚开始时候的数据。

        从此处可以看出来,即使事物B中途插入了一条数据,但是事物A前后两次查询的结果集都是一样的。 


 当前读是如何避免幻读的

      当前读的应用场景 

       在MySQL当中,除了普通查询(select语句)是快照读,其他的一律都是当前读

       也就是insert、update、delete;原因就是:假如需要修改一条记录:也就是执行update语句。

       但是,另外一个事物已经执行了delete语句进行删除了。这样的情况下面,如果update不是快照读,而是当前读,那么这将是一个没有意义的操作。因此,当前读可以保证读取到的数据都是最新的状态。


       间隙锁

       假设,表当中有一个id范围是(3,5)的间隙锁,其他的事物就无法插入id为4的数据了。这就是间隙锁的效果,它确实可以有效地防止幻读现象的发生。

        举个例子:

时间轴事物A事物B
t1开启开启
t2select*from t_text where id>100 for update
t3insert into t_text(id...) values (101...)【发生阻塞】
t4
t5提交

         可以看到,在t1时刻开启的事物,由于使用了select*from t_text where id>100 for update

  使用的就是当前读,它加了锁,也就是让id范围在(100,+∞)加上了next-key lock。(next-key lock是间隙锁+记录锁的组合)

         因为事物B提交的记录正好在这个间隙锁的范围之内,因此事物B会发生阻塞等待,直到事物A提交数据。


在可重复读的隔离级别下面幻读真的被完美解决了吗

 其实是还没有被完全解决的,有以下两个场景

  第一个场景:同一个事物,两次查询之间对于其他事物新增的数据进行了修改

  假如某一张表t_text,有如下4个数据:

idname
1小王
2小明
3小李
4小赵

此时,在可重复读的隔离级别下面,开启了两个事物。一个是事物A,另外一个是事物B

时间轴事物A事物B
t1开启
t2

select*from t_text where id=5

//没有数据

t3

开启;

insert into text(id,name) values(5,'小梅');

提交

t4update t_text set name='李鑫' where id=5
t5

select*from t_text where id=5;

//输出:

5,李鑫

       可以看到一种非常"违和"的场景,即使事物A当中没有新增一行数据。

       但是,却莫名其妙地在两次select之间新增了一行数据,也就是id为5的这一行数据。原因是什么呢?

        分析一下:

        在t4时刻,由于修改了id为5这一行的数据。因此,会把id为5这一行的数据的trx_id修改为自己的事物id,在t5时刻,再次使用select语句查询的时候,会发现,此时由于这一行的trx_id正好为自己的事物id,因此可以查询到这一行数据,也就发生了幻读。


在上述场景当中,因为是可重复读,事物A的Read View和事物B的Read View可以作出下面的假设:

事物A事物B
事物id:51事物id:52
最小活跃事物id:51最小活跃事物id:51
活跃事物id:[51]活跃事物id:[51,52]
全局最大活跃事物id:52全局最大活跃事物id:53

       时间历程

时间轴事物A事物B
t1开启
t2

select*from t_text where id=5

//没有数据

t3

开启;

insert into text(id,name) values(5,'小梅');

提交

t4update t_text set name='李鑫' where id=5
t5

select*from t_text where id=5

//输出:

5,李鑫

       t2时刻,因为查询不到这一行数据,因此没有输出。

       t3时刻,事物B插入了一条数据,因此,插入的id为5的那一行数据的trx_id的值为事物B的id,也就是52。

        t4时刻,事物A对于这一行数据进行修改。因为修改操作,不受trx_id的限制。因此,在t4时刻,事物A会把这一行数据的trx_id改变为51.

        那么,在t5时刻,将会根据事物A开始时候创建的快照来进行判断(事物A的id为51,trx_id为51,可以进行读取,也就读到了id为5的这一行数据)。

        如果在t4时刻,当前事物A不修改id为5的值的话,那么事物A就会沿着版本链,找到小于事物A的Read View的事物id的第一条记录


  第二个场景,使用范围查询的时候,不用select...for update来查询

   两次查询语句之间,另外一个事物提交了新增的数据。 

时间轴事物A事物B
T1开启
T2select * from t_test where id > 100 得到了 3 条记录。
T3

开启

插入id=200的一行数据

T4

select * from t_test where id > 100 for update

得到4条语句

由于在T4时刻,第二次使用了当前读,那么这个Read View对于当前读来说就失效了。也就是select...for update读取的是最新的数据。并且事物B已经提交了,因此就不会阻塞事物A使用select...for update进行读取。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值