快速解“锁”MySQL,拿下这7把钥匙,便能撬倒面试官

前言MySQL 作为使用范围最广的开源关系型数据库,是每个后端开发人员都绕不开的一道坎 。我在上一篇文章中也写了关于 MySQL 中的 MVCC 的细节及各个隔离级别如何使用 MVCC,有兴趣的可以查看 。
这一篇文章则是跟 MySQL 中的锁有关,锁是在并发程序中最经常使用的手段之一,但是锁的滥用也会给程序的性能带来极大的负担 。而我们平时使用 MySQL 做增删改查操作的时候,感觉不到我们有在使用锁,实际上是因为 MySQL 已经为我们使用了相关的锁 。如果你想知道我们平时使用的 SQL 语句都使用了哪些锁?都是怎么加锁的?这些锁的作用是什么?那么可以继续往下看 。

快速解“锁”MySQL,拿下这7把钥匙,便能撬倒面试官

文章插图
 
普通锁InnoDB 实现了标准行级锁,而行级锁有两种类型:
  • 共享锁(shared lock,以下将会简称为 S 锁):意在共享 。也就是允许多个事务共同持有一个记录的共享锁,该锁主要用于读取操作 。
  • 排他锁(exclusive lock,以下将会简称为 X 锁):意在排斥 。只能允许一个事务持有一个记录的排他锁,该锁主要用于更新和删除操作 。
如果你有了解过 JAVA 中的 JUC 包,那么你就会发现这有点像 JUC 中的读写锁 ReentrantReadWriteLock 。它们的目的都是为了提高读取操作的并发性 。
如果有一个事务 T1 持有行 r 的 S 锁,并且同时有另一个事务 T2 想要获取行 r 中的锁,T2 获取不同的锁将会有如下的情况发生:
  1. 假如 T2 想要获取行 r 的 S 锁,那么 T2 将会立刻得到该锁 。
  2. 假如 T2 想要获取行 r 的 X 锁,那么 T2 则会被阻塞,直到 T1 释放了行 r 的 S 锁 。
如果有一个事务 T1 持有性 r 的 X 锁,并且同时有另一个事务 T2 想要获取行 r 中的锁,不管 T2 获取什么锁都会被阻塞 。
X 锁与 S 锁的兼容性如下图所示:
快速解“锁”MySQL,拿下这7把钥匙,便能撬倒面试官

文章插图
 
最左边是持有的锁,最上面是想要申请的锁 。从图中可以看出,只要跟 X 锁相关的,都会冲突,也就是会造成阻塞 。
意向锁InnoDB 允许多种粒度的锁共存,所以会有表锁和行锁共存的情况 。为了让多种粒度的锁可以共存,InnoDB 使用了意向锁 。意向锁是表级锁,它是为了表明有一个事务正在持有锁或者打算申请一个锁 。
意向锁有两种类型:
  • 共享意向锁(intention shared lock,以下简称 IS):表示事务持有表中行的共享锁或者打算获取行的共享锁 。
  • 共享排他锁(intention exclusive lock,以下简称 IX):表示事务持有表中行的排他锁或者打算获取行的排他锁 。
IS 和 IX 只是为了表达出一种意图,它们除了全表请求之外,不会阻塞任何操作 。它们的主要目的只是为了表示持有一个行锁,或者打算获取行锁 。
意向锁的使用规则如下:
  • 事务在获取表中的共享行锁时,需要先获取表中的 IS 锁或者等级更高的锁 。
  • 事务在获取表中的排他行锁时,需要先获取表中的 IX 锁 。
这里有一个很重要的点:就是只有获取表中的行锁时,才会需要先申请意向锁 。 如果是执行 ALTER TABLE 等需要锁定整个表的语句,是不需要申请意向锁的,可以直接去申请表级 X 锁 。
表级别下的X锁、S锁、IS 锁和 IX 锁的兼容性如下:
快速解“锁”MySQL,拿下这7把钥匙,便能撬倒面试官

文章插图
 
注意:这里的 X 锁、S 锁说的也是表级锁,不要理所当然的想成了行级锁 。
为什么会有意向锁的出现呢?我们考虑如下场景(假设不存在意向锁):
一个事务 A 想要修改表 t 中的行 r,所以 A 获取行 r 的 X 锁,事r务 A 现在持有一个行锁 。此时,有一个事务 B 想要使用 ALTER TABLE 语句修改表 t 的结构,该语句首先需要获取表 t 的 X 锁,但是此时事务 B 并不知道表中是否有行被锁住,所以它只能一行一行去遍历,然后把遍历的行也锁住,直到发现表中没有行在之前已经被锁住,现在它就可以修改表的结构了 。但是它发现表中已经存在一些行被锁住,那么它就不能修改表结构,需要等这些锁都释放 。
快速解“锁”MySQL,拿下这7把钥匙,便能撬倒面试官

文章插图
 
这里有一个大问题,最坏的情况下,需要遍历所有的行才能知道是否有行被锁住,这是非常消耗性能的,而意向锁就可以解决这个问题 。我们现在再来考虑相同场景下,意向锁如何解决这个问题:
一个事务 A 想要修改表 t 中的行 r,A 首先需要获取表 t 的 IX 锁,然后成功获取 IX 锁之后,再去申请行 r 的 X 锁,申请成功之后,事务 A 此时就持有两个锁,分别是表 t 的 IX 锁和行 r 的 X 锁 。此时,有一个事务 B 想要使用 ALTER TABLE 语句修改表 t 的结构,该语句需要获取表 t 的 X 锁,事务 B 可以查看表 t 上是否存在锁来判断表中的行是否被上锁,当发现表 t 上存在 IX 锁,事务 B 就会被阻塞,因为它知道表中已经有行被锁定,所以无法申请到表 t 的 X 锁 。


推荐阅读