0
点赞
收藏
分享

微信扫一扫

《MySQL——加锁规则(待补全,有些没看懂)》

李雨喵 2022-06-28 阅读 76


catalog

  • ​​加锁规则​​
  • ​​等值查询间隙锁​​
  • ​​非唯一索引等值锁​​
  • ​​主键索引范围锁​​
  • ​​非唯一索引范围锁​​
  • ​​唯一索引范围锁 bug​​
  • ​​非唯一索引上存在"等值"的例子​​
  • ​​limit语句加锁​​
  • ​​关于死锁​​


总结

1、查询过程中访问到的对象才会加锁,而加锁的基本单位是next-key lock(前开后闭);
2、等值查询上MySQL的优化:索引上的等值查询,如果是唯一索引,next-key lock会退化为行锁,如果不是唯一索引,需要访问到第一个不满足条件的值,此时next-key lock会退化为间隙锁;
3、范围查询:无论是否是唯一索引,范围查询都需要访问到不满足条件的第一个值为止;

加锁规则

默认这里的隔离级别是可重复读。
原则1:加锁的基本单位是next-key lock。该锁的区间是前开后闭
原则2:查找过程中访问到的对象才会加锁
优化1:索引上的等值查询,给唯一索引加锁的时候,next-key lock退化为行锁
优化2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁
bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

建表语句:

CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c` (`c`)
) ENGINE=InnoDB;

insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);

等值查询间隙锁

《MySQL——加锁规则(待补全,有些没看懂)》_sql

非唯一索引等值锁

《MySQL——加锁规则(待补全,有些没看懂)》_加锁_02

session A给索引c上c=5这一行加上读锁。

根据原则1,加锁单位是next-key lock ,因此会给(0,5]加上next-key lock.

c是普通索引,因此访问c=5后还需要向右遍历,查到c=10才放弃。

根据原则2,访问到的都要加锁,因此要给(5,10]加上next-key lock;

同时符合优化2:等值判断,向右遍历,最后一个值不满足c=5,于是退化为间隙锁(5,10);

(前面分析的(0, 5]间隙锁还是存在的,合起来存在(0, 5]和(5, 10)两个间隙锁 )

根据原则2,只有访问到的对象才会加锁,这个查询使用的是覆盖索引,并不需要访问主键索引,所以主键索引上没有加任何锁,所以session B的update语句可以执行完成。

**访问到的对象才会加锁,这个“对象”指的是列,不是 记录行。 补充一下: 加锁,是加在索引上的。 列上,有索引,就加在索引上; 列上,没有索引,就加在主键上; **

session C要插入(7,7,7)记录,会被session A 的间隙锁(5,10)锁住。

lock in share mode 只锁覆盖索引,for update 会顺便给主键索引上满足条件的行加上行锁。

总结:

锁是加在索引上的;

用lock in share mode 来给行加读锁避免数据被更新的话,就必须绕过覆盖索引的优化,在查询字段中加入索引中不存在的字段。

如将session A的语句:

select id from t where c=5 lock in share mode;

修改为

select d from t where c = 5 lock in share mode;

数据行加读锁,如果查询字段使用了覆盖索引,访问到的对象只有普通索引,并没有访问到主键索引,则不会锁主键索引。如果没有使用覆盖索引,且当前查询是for update ,update 和 delete 都是当前读,则会回表查询,访问到主键索引,这样主键索引也会加锁。

主键索引范围锁

对于表t,有两个查询语句,这两个语句加锁范围不同:

select * from t where id = 10 for update;
select * from t where id >= 10 and id < 11 for update;

这两句话逻辑上等价,但是加锁规则不一样。
《MySQL——加锁规则(待补全,有些没看懂)》_sql_03
执行流程:

1、找到第一个id=10的行,因此本该是next-key lock (5,10]。根据优化1,主键id上的等值条件,退化成行锁,只加id = 10这一行的行锁

2、范围查找继续往后找,找到id=15这一行停下来,因此需要加next-key lock (10,15].

所以,session A这时候锁的范围就是主键索引上,行锁id = 10和next-key lock(10,15]。 因为是范围查询,不是等值查询,所以不会进行优化2; 所以会出现B C的block情况。

需要注意 首次session A 定位查找id=10的行时候,是当作等值查询来判断的,而向右扫描到id=15的时候,用的是范围查询判断。

--引用:
1. 先走主键id索引, 拿出id=10的那一行, (注意这里是等值查询) 2. 再从id=10的那一行开始, 不断地往右遍历拿出每一行, 直到它的 id 不满足 大于等于10, 小于11 这个条件后, 再停止 (注意这里就是范围查询) 根据一开始的Creae table/ insert values等语句(10后面就是15), 还有再根据加锁规则(原则1, 原则2, 优化1, 优化2, bug5): 执行步骤1时, 因为是等值查询, 主键索引又是唯一索引, 根据原则1, 原则2, 优化1, 最终只加行锁10; 执行步骤2时, 因为是范围查询, 主键索引又是唯一索引, 根据原则1, 原则2, Bug5, 而不满足条件的第一个值就是15, 所以最终要加锁(10, 15]; 这一块相对还是比较繁琐的

非唯一索引范围锁

《MySQL——加锁规则(待补全,有些没看懂)》_mysql_04

唯一索引范围锁 bug

《MySQL——加锁规则(待补全,有些没看懂)》_数据库_05

非唯一索引上存在"等值"的例子

给表t插入一条新纪录

insert into t values(30,10,30);

此时表里面就有了两个c=10的行。 因为主键是唯一的, 所以不存在完全相同的两行 ,此时的索引c为:
《MySQL——加锁规则(待补全,有些没看懂)》_mysql_06
两个c=10,但是主键id不同(分别为10和30),因此这两个c=10的记录之间也是有间隙的。
这里使用delete语句来验证。delete原则和之前update原则一样。
《MySQL——加锁规则(待补全,有些没看懂)》_sql_07
session A遍历的时候,先访问第一个c=10的记录。根据原则1:这里加的是

(c = 5,id = 5) 到(c = 10,id = 10)这个next-key lock.

然后,session A向右查找,直到碰到(c=15,id=15)这一行,循环才结束。根据优化2,这是一个等值查询,向右查找到了不满足条件的行,所以会退化成(c=10,id=10)到(c=15,id=15)的间隙锁。

也就是说,这个delete语句在索引c上的加锁范围,如下:
《MySQL——加锁规则(待补全,有些没看懂)》_数据库_08
注意(c=5,id=5)和(c=15,id=15)这两行都没有锁。

limit语句加锁

《MySQL——加锁规则(待补全,有些没看懂)》_mysql_09

关于死锁

next-key lock实际上是间隙锁和行锁加起来的结果。

《MySQL——加锁规则(待补全,有些没看懂)》_数据库_10
分析流程:

1、session A启动事务执行查询语句加lock in share mode,在索引c上加了next-key lock(5,10]和间隙锁(10,15);

2、session B的update语句在索引c上加next-key lock(5,10],进入锁等待;

3、然后session A要再插入(8,8,8)这一行,被session B的间隙锁锁住。由于出现了死锁,InnoDB让session B回滚。

我们认为session B的加锁还没申请成功。

但是,其实session B的"加next-key lock(5,10]"操作实际上分成了两步,先是加(5,10)的间隙锁,加锁成功;然后加c=10的行锁,第二步才被锁住。

也就是说我们分析加锁的具体步骤时,需要分成间隙锁和行锁两段来执行。


举报

相关推荐

0 条评论