Mysql中锁的分类和加锁规则

219 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第30天,点击查看活动详情

1锁的分类,引出锁的粒度

首先锁从态度上来分,分为乐观锁和悲观锁,乐观锁和悲观锁并不是锁,而是锁的设计思想。

其次锁从粒度上来分,对于不同的存储引擎,表又有三种不同的锁粒度,如下:(A代表支持)

image-20220524114659534.png

三种级别的锁:

  • 表锁:当更新数据库时,如果没有触发索引,即全表扫描,就会锁整张表;开销小,加锁快,不存在死锁,但是并发冲突概率高,并发效率低。
  • 行锁:当更新数据库时,如果触发了索引,就会锁定更新的那些行。冲突概率低,并发效率高,但是存在死锁。
  • 页面锁:开销和加锁时间界于表锁和行锁之间;锁定粒度界于表锁和行锁之间,并发度一般,存在死锁。

最后,对于Mysql来说实现了两种行级锁:

  • 共享锁:允许事务读一行数据,一般记为S,也称为读锁
  • 排他锁:允许事务删除或者更新一行数据,一般记为X,也称为写锁

2行锁(记录锁、间隙锁、临键锁)

InnoDB对于行的查询都是采用了Next-Key Lock的算法,锁定的不是单个值,而是一个范围。但是,当查询的索引含有唯一属性的时候,Next-Key Lock 会进行优化,将其降级为Record Lock,即仅锁住索引本身,不是范围。

Next-Key Lock实际上就是相当于Record Lock+Gap Lock的组合。比如索引有10,20,30几个值,那么被锁住的区间可能会是(-∞,10],(10,20],(20,30],(30,+∞)。

根据行锁的具体锁实现或根据行锁的范围又可分为三种锁:

记录锁、间隙锁、临键锁。(行锁中有这三种锁机制,更新数据库触发了索引,才会有行锁,有了行锁才会看具体的锁实现)

  1. 记录锁:

    记录锁锁的是表中的某一条记录,记录锁的出现条件必须是精准命中索引并且索引是唯一索引

  2. 间隙锁(在事务隔离级别至少为Repeatable read的时候,并且查询条件是某个范围的时候,才会用到这种锁实现,这个在文章下面讲事务隔离级别的时候会具体讲到,慢慢看,先了解一下)

    间隙锁又称之为区间锁,每次锁定都是锁定一个区间,隶属行锁。当我们查询数据用范围查询而不是相等条件查询时,查询条件命中索引,即使是没有查询到符合条件的记录,此时也会将查询条件中的范围数据进行锁定(即使是范围库中不存在的数据也会被锁定),所锁定的区间是一个左开右闭的区间。

image-20220620163221432.png

  1. 临键锁(一个bug)

    上面我们知道,间隙锁所锁定的区间是一个左开右闭的集合,而临键锁锁定是当前记录的区间和下一个记录的区间。

3.加锁规则

关于加锁的规则,丁奇老师在《MySQL 45 讲》中的总结:(看下这个规则就能很好的理解间隙锁和临键锁了)

  1. 原则 1:加锁的基本单位是 next-key lock。希望你还记得,next-key lock 是前开后闭区间。
  2. 原则 2:查找过程中访问到的对象才会加锁。
  3. 优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁(行锁的实际锁实现为记录锁)。
  4. 优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁(行锁的实际锁实现为间隙锁)。
  5. 一个bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。(行锁的实际锁实现为临键锁)

新:

  1. 原则 1:加锁的基本单位是 next-key lock。next-key lock 是前开后闭区间。
  2. 原则 2:只有访问到的对象才会加锁。(覆盖索引不会访问到主键索引上)
  3. 优化 1:索引上的等值查询,
    • 命中唯一索,退化为行锁。
    • 命中普通索引,左右两边的GAP Lock + Record Lock。
  4. 优化 2:索引上的等值查询,
    • 未命中,所在的Net-Key Lock,退化为GAP Lock 。
  5. 索引在范围查询:
    • 1.等值和范围分开判断。
    • 2.索引在范围查询的时候都会访问到所在区间不满足条件的第一个值为止。 - 3.如果使用了倒叙排序,按照倒叙排序后,检索范围的右边多加一个GAP。 哪个方向还有命中的等值判断,再向同方向拓展外开里闭的区间。