赞
踩
本文为博主对《小林coding》的学习笔记,仅供参考,具体内容请参见文章转载
目录
第一个场景:同一个事物,两次查询之间对于其他事物新增的数据进行了修改
第二个场景,使用范围查询的时候,不用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 | 查询事物开启 | 无 |
t2 | select*from t_text where id>100 | 5 |
t3 | select*from t_text where id>100 | 6 |
t4 | commit | 无 |
实现的方式是,开启事物之后,在第一次查询之后,会创建一个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 | 开启 | 开启 |
t2 | select*from t_text where id>100 for update | |
t3 | insert 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个数据:
id | name |
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,'小梅'); 提交 | |
t4 | update 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,'小梅'); 提交 | |
t4 | update 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的第一条记录
两次查询语句之间,另外一个事物提交了新增的数据。
时间轴 | 事物A | 事物B |
T1 | 开启 | |
T2 | select * 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进行读取。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。