零、前言
MYSQL innodb 中的锁在锁的级别上一般分为排他锁和共享锁;在锁共享资源的粒度上分为表级锁和行级锁;当多个事务访问同一个共享资源时,每个事务都直接获取锁,就有可能会造成相互阻塞,甚至死锁,因此为了解决相关问题 MYSQL 引入了意向锁机制,意向锁是一种实现锁协议的机制,旨在解决不同颗粒度间的并发问题。注:本文 MYSQL 版本以 5.7.31 为例
一、行级锁定义
行级锁,行级锁根据粒度,又可以分为:
Record Lock 记录锁;
Gap Lock 间隙锁,锁索引记录之间的间隙;
Next-Key Lock 记录锁+间隙锁,范围左开右闭;
1、Record Lock 记录锁
加载索引记录上的锁,需要注意的是锁的不是这行记录,而是锁索引记录, Record Lock 锁且仅锁索引(没有索引怎么办?Innodb 会创建一个隐藏的聚簇索引 -> 即一张表中有主键,用主键;没有主键,则会选择一个唯一的非空索引,如果没有合适的唯一的非空索引,则会创建一个隐藏的主键作为聚簇索引)。
例如:select C1 from T where C1 = 10 for update;
2、Gap Lock 间隙锁
在索引之间的间隙上锁,Gap 指的是 Innodb 的索引数据结构中可以插入新值的位置。
例如:update T set C2 = 1 where C1 > 10 and C1 < 20;
3、Next-Key Lock
记录锁和间隙锁的组合。
假设一个索引包含值 10、11、13 和 20,那么该索引可能的 Next-Key 锁包括的以下区间:
(-∞,10](10,11](11,13](13,20](20,+∞]
综上,对于 select for update、lock in share model、update、delete 等语句处理时,除了对唯一索引的唯一搜索外都会获取 gap 锁或 Next-Key 锁,即锁住扫描范围;gap 锁或 Next-Key 锁都是在 RR 级别下才会生效。
二、行级锁规则
了解了行级锁的分类及概念,那么行级锁到底该怎么加?即加锁原则是什么?
查阅了诸多网上资料,学习了大佬的《MYSQL 实战 45 讲》之后,我理解加锁规则是:两个原则,两个优化 和 一个“bug”
原则 1:加锁的基本单位是 Next-Key Lock ,范围左开右闭;
原则 2:查找过程中访问到的对象才会加锁;
优化 1:索引上的等值查询,给唯一索引加锁时,Next-Key Lock 可能会退化为记录锁(等值查询如果结合了范围查询,要根据所在区间结合范围查询条件判断);
优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件时, Next-Key Lock 退化为间隙锁;
一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。
1、主键索引情况
例如,索引情况如下:
(1)当我们执行 update T set d = d+1 where id = 7;由于表 T 中没有 id = 7 的记录,所以
根据原则 1 加锁基本单位是 Next-Key Lock,范围左开右闭,加锁的范围是(5,10]
根据原则 2 不存在 id=7 的记录,无法给 id = 7 的对象加锁,并且优化 1 也无法命中
根据优化 2 这是一个等值查询(id = 7)而 id = 10 并不满足查询条件,Next-Key Lock 退化为间隙锁(5,10)
因此最终加锁(5,10)。
(2)当我们执行 select * from T where id >=10 and id <11 for update;
根据原则 1 加锁基本单位是 Next-Key Lock,范围左开右闭,可能的加锁区间为(5,10],由于 id 是唯一索引,所以判断到 id=10 这一行就停止了
一个 bug 唯一索引上的范围查询会访问到不满足条件的第一个值为止,即会访问到 id = 15 这一行
根据原则 2 访问到的都要加锁,因此加锁区间为(5,10],(10,15]
根据优化 1 索引上的等值查询(id = 10)(5,10] 结合 10
因此最终加锁(10,15] 和 id = 10。
(3)当我们执行 select * from T where id >10 and id <=15 for update;
根据原则 1 加锁基本单位是 Next-Key Lock 范围左开右闭,可能的加锁区间为(10,15],由于 id 是唯一索引,所以判断到 id=15 这一行就停止了
一个 bug 唯一索引上的范围查询会访问到不满足条件的第一个值为止,即会访问到 id = 20 这一行
根据原则 2 访问到的都加锁,因此加锁区间为(10,15],(15,20]
根据优化 1 索引上的等值查询(id = 15),(10,15] 结合 10
根据优化 2 索引上的等值查询(id = 15),(10,15] 向右遍历最后一个值满足等值条件,Next-Key Lock 不会退化为间隙锁
因此最终加锁(10,15],(15,20]。
2、普通索引情况
再例如,索引情况如下:
(1)当我们执行 select id from T where c = 5 lock in share mode;
根据原则 1 加锁基本单位是 Next-Key Lock 范围左开右闭,可能的加锁区间为(0,5],而 c 是非唯一索引,访问到 c=5 这一行时不能马上停下来,要访问到 c=10
根据原则 2 访问到的都要加锁,因此(5,10]也要加锁
根据优化 1 只有唯一索引才会退化成记录锁,优化 1 不会命中
根据优化 2 索引等值判断,向右遍历,最后一个值不满足 c=5,退化成间隙锁(5,10)
根据原则 2 访问到的对象才会加锁,这个查询使用了索引覆盖,不需要访问主键索引,所以主键索引不用加锁
因此最终为普通索引加锁 (0,5] (5,10)。
(2)当我们执行 select id from T where c >=10 and c < 11 lock in share mode;
根据原则 1 加锁基本单位是 Next-Key Lock 范围左开右闭,可能的加锁区间为(5,10] (10,15]
根据原则 2 访问到的都要加锁,因此(5,10] (10,15]都要加锁
根据优化 1 只有唯一索引才会退化成记录锁,优化 1 不会命中
根据优化 2 索引等值判断,向右遍历,最后一个值可以满足 c=10,所以不会退化成间隙锁
因此最终加锁(5,10] (10,15]。
三、总结
InnoDB 的 RR 级别中,加锁的基本单位是 Next-Key Lock,只要扫描到的数据都会加锁,唯一索引上的范围查询会访问到不满足条件的第一个值为止。同时为了提升性能,也有两个优化点:
索引上的等值查询,给唯一索引加锁,Next-Key Lock 退化为记录锁。
索引上的等值查询,向右遍历且最后一个值不满足等值条件,退化为间隙锁。
作者:张宏
转载此文是出于传递更多信息目的。若来源标注错误或侵犯了您的合法权益,请与本站联系,我们将及时更正、删除、谢谢。
https://www.414w.com/read/780537.html