rr下select当前读如何让通过主键索引加锁
rc是读已提交,rr是可重复读
下面实验都是在windows mysql8.4.4下进行的
查看当前mysql的隔离级别:select @@transaction_isolation;

下面来探讨一下在rr级别是如何加锁的
建表语句:
create table user (
id int primary key ,
name varchar(10),
age int,
salary int);
插入数据:
insert into user (id, name, age,salary) values (1, '张三', 20,23), (2, '李四', 22,24), (3, '王五', 25,25), (8, '赵六', 18,26), (9, '孙七', 30,27);
给 age字段创建二级索引
create index xxx on user(age)
查看当前表的索引
show index from user;

现在一张表中是有主键索引和二级索引的
主键索引加锁方式(rr)
select语句
等值查询
如果当前id在表中记录存在
begin; select * from user where id = 2 for update;
使用select * from performance_schema.data_locks\g;查询当前锁的情况

可以看到这条语句是给user表加了一个意向排他锁,给 id为3这条数据加了一个纯行锁
如果当前id在表中记录不存在:
把上一个事务提交后执行如下sql
begin; select * from user where id = 4 for update;
使用select * from performance_schema.data_locks\g;查询当前锁的情况

可以看到已经给开区间为8的这个区间加了间隙锁,即(3,8)这个区间加了间隙锁
再试一下,如果分别查询id小于最小值和最大值是什么情况,存在于表中的间隙,先提交上面事务
如果查询id为0的值(小于当前表中id的最小值):
beginl; select * from user where id = 0 for update;

查询小于表中小于最小id的id时,会给右边开区间为1的间隙加锁即(负无穷大,1)
如果查询id为999的值(大于当前表中id的最大值):

supremum 表示一个伪记录,用来表示最大索引之后的记录
如果查询的值是大于当前索引值的最大记录,就会给(max(id),正无穷)这个区间加间隙锁
但是查询锁记录的时候,给我们看到的是给这个虚拟记录加了一个临建锁,可以这么认为是存在一个记录,这个记录永远不可以被查询到,就给supremum这条记录上了纯行锁,给(9,supremum)这条记录加了一个间隙锁

查询id为4
begin; select * from user where id = 4 for update;
使用select * from performance_schema.data_locks\g;查询当前锁的情况

可以看到是给有开区间为8的间隙加了间隙锁,这样也是为了防止有其他事务插入id为4的值出现幻读
范围查询
<=情况
如果范围条件端点在表中
执行如下sql
begin; select * from user where id <= 3 for update;

使用select * from performance_schema.data_locks\g;查询当前锁的情况
第一行表示的是给表加锁,这里我把它省略了

锁的情况是给id in (1,2,3)的记录都加了临建锁

如果查询条件的端点不在表中
begin; select * from user where id <= 4 for update;
查看锁的信息
select * from performance_schema.data_locks\g;

图中上锁的信息和端点在表中的相比,多了一个锁住以第一个大于查询条件的端点对应的值的值作为右端的间隙
这里需要多锁一条记录的原因是可能会有其他事务插入id为4的记录,造成幻读
如图:

锁住(3,8)这个间隙,其他事务旧无法在这个间隙插入数据了这样就可以可以防止幻读

当执行一条语句 select * from ... where id <= x for update时,如果x存在于表中会给所有小于等于x的记录加上临建锁,如果不存在于表中,会往后寻找一条数据,锁住x和这条数据对应的间隙
< 情况
如果查询的端点存在于表中
执行如下sql
begin; select * from user where id < 3 for update;
查询锁的信息
select * from performance_schema.data_locks\g;

图中给所有 id < 3的存在的记录加了临建锁,给开区间右端点为3的间隙加了间隙锁

关于这里为什么明明id是int,在两个元素之间应该不可能插入元素了,为什么还要给这个间隙加锁?
当id为int的时候,确实是不可能往这个间隙插入元素了,但mysql的设计是通用的,如果主键是小数,就会有可能往2到3之前插入 id为2.5的记录,主要是为了设计统一
如果查询的端点不在表中
执行下列sql
begin; select * from user where id < 4 for update;
查看锁的信息
select * from performance_schema.data_locks\g;

和之前 <= 情况讨论的一样,相比于端点存在表中的情况,多加了一个在表中以比当前查询条件更大的第一条记录作为右端点的开区间加间隙锁,防止出现幻读
当执行 < 这种查询的时候,会给所有小于条件的在表中存在的值加锁,给小于条件的第一条数据和条件之间加上间隙锁
>=情况
如果端点在表中存在
执行如下sql
begin; select * from user where id >= 3 for update;
查看锁的信息
select * from performance_schema.data_locks\g;

图中给id为3的记录加了纯行锁,给大于 3的id对应的记录加了临建锁
这里有一个现象是 id为3的记录出现了锁退化现象

这里出现锁退化的原因是因为 我们查询 di >= 3的数据,只需要锁住 >=3的这个范围的数据不被修改即可,就没必要对开区间为3的区间加锁了
如果id的范围条件边界不存在:
有下列sql
begin; select *from user where id >= 4 for update;
查看锁的信息
select * from performance_schema.data_locks\g;

上图中锁的信息是给所有 大于等于4的记录都加上了临建锁
这里可以得出:当执行 >= 的查询是,如果边界条件的值存在的话对应值上的锁会退化为纯行锁,其他记录都加上临建锁,否则所有满足在表中满足条件的记录全部加上临键锁
>的情况
如果端点在表中存在
执行下面sql
begin; select * from user where id > 8 for update;

查看锁的信息
select * from performance_schema.data_locks\g;
这里会给表中所有大于端点值的记录全部加上临键锁,这里可以把supremum一个虚拟点,对虚拟点加锁就是给大于当前表中索引最大值的间隙加锁
如果查询的端点不存在
执行下面sql
begin; select * from user where id > 7 for update;
查看锁的信息
select * from performance_schema.data_locks\g;

上面加锁的信息可以看出给表中所有 id > 7 的对应的值加了临键锁
会给表中所有大于id对应的记录加上临键锁
总结
等值查询
- 如果查询的id不存在(主键),就给第一条大于id对应的记录左侧加间隙锁
- 如果id存在,就会给对应的id加上纯行锁
范围查询
- 大于情况:不管id是否存在,都会给表中所有大于id的记录加上临键锁
- 大于等于情况:
- id存在:给表中对应的id加上纯行锁,给所有大于id的记录加上临键锁
- id不存在:给表中所有大于id的记录都加上临键锁
- 小于情况:
- id存在:会给所有小于条件的在表中存在的值加锁,给小于条件的第一条数据和条件之间加上间隙锁
- id不存在:会给第一条小于id对应的记录和后一条数据和加上间隙锁,其他小于等于id的记录都加上临键锁
- 小于等于情况:
- id存在:如果x存在于表中会给所有小于等于x的记录加上临键锁
- id不存在:如果不存在于表中,会往后寻找一条数据,锁住x和这条数据对应的间隙,再给小于等于id的记录加上临键锁
到此这篇关于mysql在rr级别当前读如何通过主键索引加锁的文章就介绍到这了,更多相关mysql在rr级别索引加锁内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论