加锁规则
- 原则1 加锁的基本单位是next-key lock,前开后闭
- 原则2 查找过程中访问到的对象才会加锁
- 优化1 索引上的等值查询,给唯一索引加锁时,next-key lock退化为行锁(记录锁)。
- 优化2 索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁
- 一个bug 唯一索引上的范围查询会访问到不满足条件的第一个值为止
数据准备
@H_502_0@表名:t 新增数据:(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25)案例
1 等值查询间隙锁
@H_502_0@等值查询的间隙锁- 表t中无id=7,所以原则1,加锁next-key lock,session A锁范围(5,10]
- 根据优化2,等值查询(id=7),而id=10不满足,next-key lock退化成间隙锁,最终加锁范围(5,10)
- session B要往这个间隙里面插入id=8的记录会被锁住
- session C修改id=10可以
2 非唯一索引等值锁
@H_502_0@只加在非唯一索引上的锁- 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) 根据原则2 ,只有访问到的对象才会加锁,这个查询使用覆盖索引,并不需要访问主键索引,所以主键索引上没有加任何锁,所以session B的update语句可以执行完成。 但session C要插入(7,7,7),就会被session A的间隙锁(5,10)锁住。
3 主键索引范围锁
@H_502_0@范围查询。 @H_502_0@下面这两条查询语句,加锁范围相同吗?MysqL> select * from t where id=10 for update;
MysqL> select * from t where id>=10 and id<11 for update;
- id定义为int类型,这俩语句就是等价的吧? 并不完全等价。
- 主键索引上范围查询的锁
4 非唯一索引范围锁
@H_502_0@session_1 |
@H_502_0@session_2 |
@H_502_0@session_3 |
---|---|---|
@H_502_0@begin; select * from t where c>=10 and c<11 for update; |
@H_502_0@ |
@H_502_0@ |
@H_502_0@ |
@H_502_0@ insert into t values(8,8,8);(blocked) |
@H_502_0@ |
@H_502_0@ |
@H_502_0@ |
@H_502_0@update t set d=d+1 where c=15;(blocked) |
- session1在第一次用
c=10
定位记录时,索引c加了(5,10] next-key lock
- c是非唯一索引,无优化规则,即不会退变为行锁
- 因此最终sesion1加锁为c的
(5,10]
和(10,15]
next-key lock。
唯一索引范围锁bug
@H_502_0@前四案例用到两个原则和两个优化,再看加锁规则bug案例。@H_502_0@session_1 |
@H_502_0@session_2 |
@H_502_0@session_3 |
---|---|---|
@H_502_0@begin; select * from t where id>10 and id<=15 for update; |
@H_502_0@ |
@H_502_0@ |
@H_502_0@ |
@H_502_0@update t set d=d+1 where id=20;(阻塞) |
@H_502_0@ |
@H_502_0@ |
@H_502_0@ |
@H_502_0@insert into t values(16,16,16);(阻塞) |
- 按原则1,索引id只加
(10,15] next-key lock
,因为id是唯一键,所以循环判断到id=15
这行就该停止遍历。
id=20
,且由于这是范围扫描,因此id上的(15,20] next-key lock
也会被锁。
@H_502_0@所以session2要更新id=20这行会被阻塞。
session3要插入id=16,也会被阻塞。
@H_502_0@按理说锁住id=20这行没必要,因为唯一索引扫描到id=15即可确定不用继续遍历。但实现上还是这么做了,可能是个bug。
非唯一索引上存在"等值"的例子
@H_502_0@为更好地说明“间隙”概念。 插入记录76
@H_502_0@delete加锁逻辑类似select ... for update
,即也符合一开始的规则。
@H_502_0@session_1 |
@H_502_0@session_2 |
@H_502_0@session_3 |
---|---|---|
@H_502_0@begin; delete * from t where c=10 |
@H_502_0@ |
@H_502_0@ |
@H_502_0@ |
@H_502_0@insert into t values(13,13,13);(阻塞) |
@H_502_0@ |
@H_502_0@ |
@H_502_0@ |
@H_502_0@update t set d=d+1 where c=15; |
- 根据原则1,这里加是(c=5,id=5)到(c=10,id=10) next-key lock
- 然后,session1 向右查找,直到碰到(c=15,id=15)这行,循环结束。根据优化2,等值查询,向右查找到不满足条件的行,所以退化成(c=10,id=10) 到 (c=15,id=15)的间隙锁(开区间,(c=5,id=5)和(c=15,id=15)这两行无锁)。
7 limit 语句加锁
@H_502_0@session_1 |
@H_502_0@session_2 |
---|---|
@H_502_0@begin; delete * from t where c=10 limit 2 |
@H_502_0@ |
@H_502_0@ |
@H_502_0@insert into t values(13,13,13);(阻塞) |