开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 9 天,点击查看活动详情
提到锁需要先搞清楚当前隔离级别,查询语句是否涉及索引,如果存在索引,是聚簇索引还是非聚簇索引。
不过首先要搞清楚,锁的只是索引,即使表没有定义索引,innodb也会创建一个隐藏的聚簇索引,并使用该索引锁定记录。
对于一条sql语句,会加以下类型的锁:
Record Lock:行锁,对索引记录进行加锁,行锁最终都会落到聚簇索引上。
Gap Lock:间隙锁,是对索引的间隙加锁,其目的只有一个,防止其他事物插入数据。在Read Committed隔离级别下,不会使用间隙锁;当隔离级别为Repeatable Read和Serializable时,就会存在间隙锁。
Next-Key Lock:索引记录上的记录锁和索引记录之间间隙上的间隙锁组合。解决可重复读的幻读问题。锁住的是索引前面的间隙。
原则1: 加锁的基本单位是next-key lock,左开右闭区间
原则2: 查找过程中访问到的对象才会加锁
优化1: 索引上等值查询加锁时next-key lock退化为行锁
优化2: 索引上等值查询,向右遍历时且后一个值不满足等值条件时,next-key lock 退化为gap lock.
bug1: 索引上的范围查询会访问到不满足的个值为止
对于数据表,id是主键,以下探讨在rc和rr隔离级别下可能的加锁情况
| id | name | num |
|---|---|---|
| 1 | aaa | 100 |
| 5 | bbb | 200 |
| 8 | ccc | 300 |
| 10 | ddd | 400 |
1. 查询命中聚簇索引
- 对于精确查询,会对命中的索引加record lock
- 如果是范围查询
rc:在所有命中的行的聚簇索引加record lock
// 在id=8和10的聚簇索引上加X锁
update my_table set name='a' where id>7;
// 在id=1的聚簇索引上加X锁
update my_table set name='a' where id<=1;
rr:在所有命中的行的聚簇索引加next-key lock
// 在id=8、10(、+∞)的聚簇索引上加X锁
// 在(5,8)(8,10)(10,+∞)加gap lock
// 索引范围查询查到不满足条件的个值为止(id=5,
update my_table set name='a' where id>7;
// 在id=1、5的聚簇索引上加X锁
// 在(-∞,1)(1,5)加gap lock
update my_table set name='a' where id<=1;
- 如果查询结果为空
rc:无锁
rr:锁住目标所在间隙
// 在(1,5)加gap lock
update my_table set name='a' where id=2;
2. 查询命中唯一索引
- 如果是精确查询,命中的唯一索引和对应的聚簇索引加record lock。如果是覆盖查询,没有访问聚簇索引,就不会在聚簇索引上加锁
// 在num=100的唯一索引上加X锁
update my_table set name='a' where num=100;
- 如果是范围查询
rc:所有命中的唯一索引和对应的聚簇索引加record lock
rr:命中的唯一索引和对应的聚簇索引加next-key lock,最后命中的索引的最后一条记录也被加上next-key lock.对应的聚簇索引加record lock。
// 在num=100和num=200的唯一索引上加X锁
// 并在id=1和id=5的聚簇索引上加X锁
// 并在唯一索引的间隙(-∞,100)(100,200)加gap lock
update my_table set name='a' where num<150;
小结
- 读未提交/读已提交
如果是非聚簇索引,会对非聚簇索引和对应的聚簇索引加锁。 虽然通过聚簇索引扫描全表加锁,但是会过滤并释放不符合条件的锁。
- 可重复读
对非聚簇索引查询,涉及的所有非聚簇索引(包括一个不满足条件的)加gap lock,涉及的聚簇索引加行锁
对聚簇索引查询,涉及的所有聚簇索引(包括一个不满足条件的)加gap lock
参考
www.fanyilun.me/2017/04/20/…
z.itpub.net/article/det…
dev.mysql.com/doc/refman/…