1.3 REPEATABLE READ(可重复读)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了1.3 REPEATABLE READ(可重复读)相关的知识,希望对你有一定的参考价值。
参考技术A 1.设置为可重复读SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
2.首先TB做出一次查询,此时name为bb
3.TA对数据进行修改,并提交
4.此时加入session C作为对比,使用自动事务并做出查询,可见数据已经被修改
5.在已经开启事务的TB中,查询到的,仍然是数据开启事务之前的状态,所以数据是可重复读的
可重复读隔离级别中,每当事务开始后,第一次执行sql语句时(或者执行START TRANSACTION WITH CONSISTENT SNAPSHOT)MVCC会建立一个read view,选取当时的系统版本号,作为事务版本号,以Undo log的行系统版本号与事务版本号进行对比,增删改查都要遵循如下模式:
SELECT:
1, InnoDB只查找版本早于当前事务版本的数据行(行的系统版本号小于等于事务版本号),这样可以确保事务读取的行,要么是事务开始前就存在的,要么是事务自身插入的或修改过的。
2, 行的删除号要么未定义,要么大于当前事务版本号,这样可以确保事务读取到的行,在事务开始之前未被删除。
INSERT:
InnoDB 为新插入的每一行保存当前系统版本号做为行版本号。
DELETE:
INNODB 为删除的每一行保存当前系统版本号作为行删除标识。
UPDATE:
InnoDB 为插入的每一行新记录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识。
所以READ COMMITTED和REPEATABLE READ的区别在于,READ COMMITTED总是读取被锁定行的最新一份快照数据, 所以会有不可重复读的问题。而REPEATABLE READ读取事务开始时行系统版本号小于事务版本号的数据,解决不可重复读的问题。
此外要提的一点是,mysql的REPEATABLE READ与Oracle的不同,不但解决了不可重复读问题,还解决的“幻读”问题。
幻读的产生:遵循上述增删改查模式,假设TA事务的事物版本号为3,TB早于TA开启事务,TB事物版本号为2,TB先新增一条数据,行数据的版本号为2,并提交。TB提交后,TA查询时,满足“行的系统版本号小于等于事务版本号”的条件,可以查到数据,形成“幻读”。
InnoDB采用Next-key Lock(间隙锁)来避免“幻读”问题。
Record Lock:单个行记录的锁,锁定的是索引而非记录本身。
GAP Lock:间隙锁,锁定一个范围,但不包含记录本身。
Next-Key Lock:Gap Lock+Record Lock 锁定一个范围并锁定记录本身。
举例说明:
某表字段为ID和NAME,有3条睡觉,ID分别为10,20,50。
如果索引为 10,20,50,那么:
Record Lock:select * from tab where id = 10 for update; //对id=10单行进行加锁
Gap Lock锁范围:(-∞,10)(10,20)(20,50)(50,+∞)
Next-Key Lock锁范围:(-∞,10] (10,20] (20,50] (50,+∞)
当查询的索引含有唯一属性时,InnoDB会对Next-Key Lock进行优化,将其降级为Record Lock,即仅锁住索引本身,而不是范围。
虽然避免了幻读问题,但是还有个特殊情况,感觉可以叫做“幻改”,即当前事务修改了本身并不能查询到的数据。这也是MVCC造成的假象,当前事务虽然查询不到其他事务提交的数据,但是可以对其他事务提交的数据进行修改。
MySQL可重复读-问题实践
MySQL可重复读之幻读问题
MySQL事务存储引擎InnoDB的默认隔离级别为可重复读,在该隔离级别下,可以很大程度上避免幻读的问题,完全避免脏读和不可重复读问题,接下来通过实际的测试看看这些场景是否真的能够被完全避免
模拟均基于下表:
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '自增id',
`username` varchar(20) NOT NULL COMMENT '用户名',
`password` varchar(20) NOT NULL COMMENT '密码',
`name` varchar(20) DEFAULT NULL COMMENT '真实姓名',
`age` tinyint DEFAULT NULL COMMENT '用户年龄',
`address` varchar(30) DEFAULT NULL COMMENT '现居地址',
PRIMARY KEY (`id`),
KEY `idx_name_age` (`name`,`age`),
KEY `idx_username` (`username`)
) ENGINE=InnoDB
表中具有如下数据:
不可重复读问题
我们首先通过如下示例看看MySQL的InnoDB究竟是否能避免不可重复读问题
以下测试基于MySQL8
首先查看MySQL的默认隔离级别:
# 查看当前会话下的隔离级别
select @@transaction_isolation;
# 查看全局隔离级别
select @@global.transaction_isolation;
可以发现,当前会话的隔离级别为REPEATABLE-READ,可重复读
首先,我们来看看可重复读的情况下是否会出现不可重复读的问题
什么是不可重复读: 在一个事务中,前后相同的两次查询,结果应当是一样的,但是查询返回的结果确不同。也就是说,同一份数据在同一个事务中不能被重复读。出现这种情况的可能原因是,当一个事务执行某个查询后,数据被另一个事务更新,当前事务再次执行该查询会得到更新后的数据。它违背了数据库设计的ACID原则
模拟不可重复读的场景:
1、开启两个MySQL客户端连接,并且分别开启事务(分别为事务1和事务2):
开启事务的语句如下:
begin;
2、在事务1中通过如下SQL语句查询数据库的数据:
select * from user where id = 4;
结果如下:
3、在事务2中对user表中id为4的数据进行修改,并查看修改结果:
update user set password = '4' where id = 4;
select * from user where id = 4;
结果如下:
可以发现在当前事务修改已生效,在另一个事务中进行查询
4、在事务1中查询user表中id为4的数据:
select * from user where id = 4;
结果:可以发现这里password还是5,事务2的未提交的修改并未影响事务1的结果,这里可以证明可重复读避免了脏读的问题
5、事务2提交修改:
commit;
6、事务1继续进行查询:
select * from user where id = 4;
结果如下:可以发现即使事务2提交了,事务1在读取的过程中,数据也没有改变,这里可以证明可重复读避免了不可重复的问题,一个事务的修改即使提交了,也不会影响另一个事务的读取
7、事务1提交后再读取数据:
commit;
select * from user where id = 4;
结果如下:
幻读问题
MySQL可重复读隔离级别下会不会出现幻读问题,接下来在模拟中看看实际效果
什么是幻读:假设有两个事务,分别是事务A和事务B,事务A首先以某一where条件读取数据集合;随后事务B向数据库中插入满足事务A查询where条件的数据,并且提交;事务A再次以相同的where条件查询数据时,读取到的数据集合与之前读取的数据集合不同(多了一条数据或少了一条数据),这种情况就是幻读问题
模拟幻读问题:
1、开启两个客户端连接,并分别开启事务(分别为事务1和事务2):
begin;
2、事务1通过如下查询语句查询数据:
select * from user where id > 1 and id < 6;
结果如下:
3、事务2向user表中插入id为5的数据:
insert into user(id, username, password, name, age, address) values(5, '5', '5', '王五', 12, 'hubei');
select * from user;
结果如下:
可以发现当前事务已经存在id为5的数据了,然后我们直接提交事务2
commit;
再次查询:
select * from user;
结果如下:可以发现数据已经插入数据库中
4、事务1再以相同条件查询数据:
select * from user where id > 1 and id < 6;
结果如下:可以发现并未查询到id为5的数据,这里可以证明可重复读隔离级别避免了幻读问题
5、在事务1中对id为5的数据进行修改,然后再次以相同的条件进行查询:
update user set password = '6' where id = 5;
select * from user where id > 1 and id < 6;
结果如下:可以发现这次查询又查到了id为5的数据,这里就出现了幻读的问题
从上面的模拟中可以发现,MySQL可重复读隔离级别在大部分场景下是可以避免幻读问题的,但是在第5中场景下是无法避免幻读问题,也就是:
- 当事务2向数据库中插入满足事务1查询where条件的数据后,并且commit,此时事务1以相同的where条件查询时是不会读取到新插入的数据的,这就避免了幻读问题
- 当事务1修改了事务2插入的数据之后,也就是update语句修改了事务2插入的数据,再次以相同的条件查询数据库时,会发现查询出来事务2插入的数据,出现了幻读问题
总结:MySQL可重复读隔离级别在当前事务不修改其他事务插入的数据时,是可以避免幻读的;但是当前事务修改了其他事务插入的数据之后,还是会出现幻读的问题
本篇文章从实践的角度讲解了MySQL可重复读隔离级别下避免脏读、不可重复读、幻读的场景,也把可能出现的幻读问题进行模拟
接下来会从MVCC原理的角度来讲解MySQL的可重复读是如何实现的,敬请期待
以上是关于1.3 REPEATABLE READ(可重复读)的主要内容,如果未能解决你的问题,请参考以下文章
mysql repeatable-read 一次利用间隙锁解决幻读案例
眼见为实自己动手实践理解REPEATABLE READ && Next-Key Lock