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);
等值查询间隙锁
非唯一索引等值锁
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;
这两句话逻辑上等价,但是加锁规则不一样。
执行流程:
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]; 这一块相对还是比较繁琐的
非唯一索引范围锁
唯一索引范围锁 bug
非唯一索引上存在"等值"的例子
给表t插入一条新纪录
insert into t values(30,10,30);
此时表里面就有了两个c=10的行。 因为主键是唯一的, 所以不存在完全相同的两行 ,此时的索引c为:
两个c=10,但是主键id不同(分别为10和30),因此这两个c=10的记录之间也是有间隙的。
这里使用delete语句来验证。delete原则和之前update原则一样。
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上的加锁范围,如下:
注意(c=5,id=5)和(c=15,id=15)这两行都没有锁。
limit语句加锁
关于死锁
next-key lock实际上是间隙锁和行锁加起来的结果。
分析流程:
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的行锁,第二步才被锁住。
也就是说我们分析加锁的具体步骤时,需要分成间隙锁和行锁两段来执行。