浅析mysql的加锁

104 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 9 天,点击查看活动详情
提到锁需要先搞清楚当前隔离级别,查询语句是否涉及索引,如果存在索引,是聚簇索引还是非聚簇索引。
不过首先要搞清楚,锁的只是索引,即使表没有定义索引,innodb也会创建一个隐藏的聚簇索引,并使用该索引锁定记录。

对于一条sql语句,会加以下类型的锁: Record Lock:行锁,对索引记录进行加锁,行锁最终都会落到聚簇索引上。
Gap Lock:间隙锁,是对索引的间隙加锁,其目的只有一个,防止其他事物插入数据。在Read Committed隔离级别下,不会使用间隙锁;当隔离级别为Repeatable ReadSerializable时,就会存在间隙锁。

Next-Key Lock:索引记录上的记录锁和索引记录之间间隙上的间隙锁组合。解决可重复读的幻读问题。锁住的是索引前面的间隙。

原则1: 加锁的基本单位是next-key lock,左开右闭区间
原则2: 查找过程中访问到的对象才会加锁
优化1: 索引上等值查询加锁时next-key lock退化为行锁
优化2: 索引上等值查询,向右遍历时且后一个值不满足等值条件时,next-key lock 退化为gap lock. bug1: 索引上的范围查询会访问到不满足的个值为止

对于数据表,id是主键,以下探讨在rc和rr隔离级别下可能的加锁情况

idnamenum
1aaa100
5bbb200
8ccc300
10ddd400

1. 查询命中聚簇索引

  • 对于精确查询,会对命中的索引加record lock
  • 如果是范围查询

rc:在所有命中的行的聚簇索引加record lock

// 在id=810的聚簇索引上加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=810(、+∞)的聚簇索引上加X锁
// 在(5,8)(8,10)(10,+∞)加gap lock
// 索引范围查询查到不满足条件的个值为止(id=5,
update my_table set name='a' where id>7;  

// 在id=15的聚簇索引上加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/…