锁概念
InnoDB支持几种不同的行级锁: 1、行锁(Record Lock): 对索引记录加锁。 2、间隙锁(Gap Lock): 锁住整个区间,包括:区间里具体的索引记录,不存在的空闲空间(可以是两个索引记录之间,也可能是第一个索引记录之前或最后一个索引记录之后的空间)。 3、next-key锁: 行锁和间隙锁组合起来。
间隙锁
Gap Lock的唯一目的就是阻止其他事务插入到间隙中(间隙锁是在可重复读隔离级别下才生效
)
丁奇老师文章中总结到加锁的2个原则和2个优化如下,下面针对实例验证
- 原则1:加锁的基本单位是next-key lock(next-key lock是前开后闭区间)。
- 原则2:查找过程中访问到的对象才会加锁。
- 优化1:索引上的等值查询,给唯一索引加锁的时候,next-key lock退化为行锁。
- 优化2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁。
创建示例表用户演示
CREATE TABLE `t` (
`id` int(11) NOT NULL PRIMARY KEY,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
KEY `c` (`c`)
) ENGINE=InnoDB;
初始化如下数据
id | c | d |
---|---|---|
0 | 5 | 5 |
10 | 10 | 10 |
15 | 15 | 15 |
20 | 20 | 20 |
25 | 25 | 25 |
主键等值查询间隙锁
SessionA:
begin; // 开事务
update t set d = d+1 where id =7 ; //执行SQL正常
SessionB:
insert into t values(9,9,9); //执行SQL阻塞
SessionC:
update t set d = d+1 where id =10 ; //执行SQL正常
SessionB为何阻塞?
更新t表中id=7这行记录,根据加锁规则分析
- 根据规则1分析,坐开右闭加锁区间 即(5,10]
- 原则2中提到遇到访问对象加锁,更新条件是id =7 而 id =10不满足条件,所以next-key lock 转为间隙锁 ,锁区间(5,10)
所以上述现象中SessionB看见阻塞,而SessionC正常执行。
主键索引范围锁
SessionA:
begin; // 开事务
select * from t where id >= 10 and id < 11 for update ; //执行SQL正常
SessionB:
insert into t values(7,7,7); //执行SQL正常
insert into t values(14,14,14); //执行SQL阻塞
SessionC:
update t set d = d+1 where id =15 ; //执行SQL阻塞
分析SessionA是如何加锁
- Session A 查找id = 10 这条记录,根据规则1可知加锁加锁范围(5,10],由于 id是主键唯一,找到id =10后 ,退化为行锁,只对 id=10这行加锁
- 由于是范围查询,继续向后查询直到id=15这行停止,所以加锁范围(10,15]
所以执行insert into t values(14,14,14)和update t set d = d+1 where id =15
是有锁等待。
非唯一索引范围锁
t表中c字段为非唯一索引,按c字段范围查询
SessionA:
begin; // 开事务
select * from t where c >= 10 and c < 11 for update ; //执行SQL正常
SessionB:
insert into t values(7,7,7); //执行SQL阻塞
SessionC:
update t set d = d+1 where c =15 ; //执行SQL阻塞
分析SessionA是如何加锁
- 查询c >= 10 首先增加了(5,10] 间隙锁,由于 c不是唯一索引,所以不会退化为行锁,继续向由查找到 c=15终止查找,所以在c上加锁范围(5,10] 和(10,15]
所在在执行insert into t values(7,7,7);是锁等待,执行update t set d = d+1 where c =15 也是同样原理。
其他场景也可按上述规则分析。