学习之前的疑问:
可重复读的概念 与 不可重复读的概念
行锁+gap锁(间隙锁)
快照读 与 当前读区别
快照读中的MVCC
当前读中的gap锁 和 行锁
前几天面试时被问到了mysql可重复读如何解决幻读的问题,之前脑子中的概念只有增加了共享锁和排他锁进行避免,没有实践或者清晰的概念,今天首先实践验证和理解mysql下可重复读对于幻读的避免和原理。参考博文:https://blog.csdn.net/Y0Q2T57s/article/details/103708206
首先整体了解数据库的隔离级别:
读未提交 -------------- 可能产生脏读,幻读,不可重复读
读已提交(不可重复读)----------------- 可能产生不可重复读,幻读
可重复读 ---------------------------------可能产生幻读
序列化 -----------------------------------无
可重复度和不可重复读
- 概念:
- 不可重复读指的是事务1和事务2同时获取数据,在此时事务2进行插入行或者修改行操作,这时事务1再次进行select时会发现多了几行数据或者数据不一致,这就称为幻读
- 可重复读,也就是在RR隔离级别下,为了避免不可重复读的幻读情况发生,事务2修改数据的结果 在事务1是无法获取的,进行了隔离,这个实现后面会介绍到MVCC。
在了解了mysql的几个隔离级别后,针对可重复读是如何防止产生幻读的?
- 先来通过实践验证简单的:如何防止如不可重复读的情况
首先创建一张表
Create Table: CREATE TABLE `dept` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8
开启两个事务
表中没有数据,现在我们往右边事务中插入一条数据,如果在不可重复的隔离级别下在左边事务再select会发现新插入数据,而可重复读的情况下左边的select数据一直不变。
- 解释:这一现象的原理是RR即可重复读的隔离级别下用到了MVCC,多版本并发控制,每个事务在开启时会有一个版本号,不管其他事务如何数据,而当前事务中的版本号不会变化,所以左边事务一直select的数据都是一个版本中的,所以不会读取到右边事务插入后的数据。
在左边事务中发现内容为空,我们插入一个id=29的数据行,看看会发生什么
- 解释:这是什么情况呢?这是因为在select时会使用到快照读,但是在例如上面insert时会进行当前读, 如字面意思解释,快照读即事务开启时产生的版本号一般,进行了数据表当前的数据“复制”,在事务中select时只会用到快照读,但是如果进行insert/ update / select … for update 这种语句时,会进行当前读,访问数据表中“最新”的数据,这时尽管右边的insert未提交,但当前读还是会读到, 那么根据报错的信息,lock,得知在右边事务insert一行之后会进行加锁 (X锁),不允许其他事务修改,但能读取当前数据(当前读)即获取当前行的S锁,左边insert需要在右边事务释放锁之后才能进行insert/update操作(commit或者rollback)
下面对X锁和S锁进行学习
- X锁称为排他锁(悲观锁) ----- 对目标行添加当前事务的X锁,即可修改或删除操作
- S锁称为共享锁(共享锁) ----- 对目标行添加当前事务的S锁,即可读当前行的数据
S锁是可以兼容的,即多个事务都可以持有对某行的读权限, 而X锁是不兼容的,即最多只有一个事务能获取当前行的修改或删除权限, 若其他事务需要获取X锁,必须等当前拥有X锁权限的事务释放X锁才能获取。
而上面出现的情况是因为 右边事务在Insert时便对id=29的行数据添加了X锁,而这时左边事务要对id=29的行进行X操作,那么首先需要获取已经被右边事务拥有的X锁,那么需要等右边事务释放X锁权限(commit或者rollback)才能获取.
- 还有如下情况:
如上面解释,如果右边事务插入id=99的数据行,如果左边事务修改id范围不包含99的话应该可以操作,但是左边事务也遇到了锁,导致不能修改成功,这是因为上面的X锁不仅包含了当前一行,还有(-无穷大, 10) 和 (10,+无穷大)加锁, 在左边事务update时,会遇到间隙锁(GAP锁),导致需要等待右边事务释放锁后才能更新。