大家好,我是鸭鸭!
今天给大家带来一个非常硬核且常见的面试题解析。
这个答案节选自鸭鸭最近弄的面试鸭小程序,更多硬核高频面试题可以点击面试鸭进行阅读哈!
鸭鸭先给大家打个预防针,这篇解析比较长,大家需要有点耐心,MySQL 锁不仅在面试中常见,在我们业务使用上也非常频繁,因此对它的理解也至关重要。
共享锁和排他锁
在 MySQL 中锁可以分为两大类,分别是 shared (S) locks 和 exclusive (X) locks。
- S锁,称为共享锁,事务在读取记录的时候获取 S 锁,它允许多个事务同时获取 S 锁,互相之间不会冲突。
- X锁,称为独占锁(排他锁),事务在修改记录的时候获取 X 锁,且只允许一个事务获取 X 锁,其它事务需要阻塞等待。
S 锁之间不冲突,X 锁则为独占锁,所以 X 之间会冲突, X 和 S 也会冲突。
冲突 | S | X |
---|---|---|
S | 不冲突 | 冲突 |
X | 冲突 | 冲突 |
SELECT ... LOCK IN SHARE MODE;
这种读取需要对记录上 S 锁。
SELECT ... FOR UPDATE;
需要对记录上 X 锁。
MyIASM 引擎仅支持表锁,而 Innodb 即支持表锁也支持行锁。
LOCK TABLES yes READ
是对 yes 这个表上 S 锁。LOCK TABLES yes WRITE
是对 yes 这个表上 X 锁。
不过一般情况下,我们不会使用表锁,因为平日的 UPDATE 、SELECT 要用也是用行锁了,不可能用粒度粗的表锁,不然性能太低。
唯一能想到用上表锁的就是 DDL 语句了,比如 ALTER TABLE 的时候,应该锁定整个表,防止查询和修改,但是这个 server 已经提供了一个叫 MDL 的锁,即 Metadata Locks
(元数据锁)。
元数据锁
元数据锁也分为读锁和写锁:
1)读锁 (MDL_SHARED):
当一个事务需要读取表的元数据时(如执行 SELECT 操作),会获取读锁。
多个事务可以同时持有读锁,不会互相阻塞。
2)写锁 (MDL_EXCLUSIVE):
当一个事务需要修改表的元数据时(如执行 ALTER TABLE 操作),会获取写锁。
写锁会阻塞其他任何读锁和写锁,确保独占访问。
元数据锁的主要作用如下:
1)防止并发的 DDL 操作和 DML 操作冲突:
- 当一个事务对表进行结构性更改(如 ALTER TABLE),元数据锁(写锁)会阻止其他事务对该表进行操作,直到结构更改完成。
- 当一个事务对表进行数据操作(如 SELECT、INSERT、UPDATE、DELETE),元数据锁(读锁)会阻止其他事务对该表进行结构性更改。
2)保护元数据一致性:
- 确保在执行 DDL 操作(如 CREATE TABLE、DROP TABLE、ALTER TABLE)时,元数据不会被其他事务同时修改。
假设业务上真用到了表锁,那么表锁和行锁之间肯定会冲突,那么 InnoDB 加表锁的是偶,如果判断表里面是否已经有行锁?难道得一条记录一条记录遍历过去找吗?
显然这样的效率太低了!
意向锁
所以有了个叫意向锁(Intention Locks)的东西。
- IS(Intention Shared Lock),共享意向锁
- IX(Intention Exclusive Lock),独占意向锁。
这两个锁是表级别的锁,当需要对表中的某条记录上 S 锁的时候,先在表上加个 IS 锁,表明此时表内有 S 锁。当需要对表中的某条记录上 X 锁的时候,先在表上加个 IX 锁,表明此时表内有 X 锁。
这样操作之后,如果要加表锁,就不需要遍历所有记录去找了,直接看看表上面有没有 IS 和 IX 锁。
比如,此时要上表级别的 S 锁,如果表上没有 IX ,说明表中没有记录有独占锁,其实就可以直接上表级 S 锁。
如果此时要上表级别的 X 锁,如果表上没有 IX 和 IS ,说明表中的所有记录都没加锁,其实就可以直接上表级 X 锁。
因此 IS 和 IX 的作用就是在上表级锁的时候,可以快速判断是否可以上锁,而不需要遍历表中的所有记录。
所以 IS 和 IX 互相之间是不会冲突的,因为它们的作用只是打个标记,来丰富一下上面的表格:
冲突 | S | X | IS | IX |
---|---|---|---|---|
S | 不冲突 | 冲突 | 不冲突 | 冲突 |
X | 冲突 | 冲突 | 冲突 | 冲突 |
IS | 不冲突 | 冲突 | 不冲突 | 不冲突 |
IX | 冲突 | 冲突 | 不冲突 | 不冲突 |
至此,已经理解了共享锁、独占锁、表锁相关的知识,接下来再来看看 MySQL 中的行锁有哪几种。
主要有三种:
- 记录锁(Record Locks)
- 间隙锁(Gap Locks)
- 临键锁(Next-Key Locks)
记录锁
记录锁顾名思义就是锁住当前的记录,它是作用到索引上的。我们都知道 innodb 是肯定有索引的,即使没有主键也会创建隐藏的聚簇索引,所以记录锁总是锁定索引记录。
比如,此时一个事务 A 执行 SELECT * FROM yes WHERE name = 'xx' FOR UPDATE;
那么 name = xx 这条记录就被锁定了,其他事务无法插入、删除、修改 name = xx 的记录。
此时事务 A 还未提交,另一个事务 B 要执行 insert into yes (name) values ('xx')
,此时会被阻塞,这个很好理解。
但是,如果另一个事务 C 执行了 insert into yes (name) values ('aa')
,这个语句会被阻塞吗?
看情况。
如果 name 没有索引。前面提到记录锁是加到索引上的,但是 name 没索引啊,那只能去找聚簇索引,但聚簇索引上面只有主键啊,它哪知道各自的 name 是什么,所以咋办?都锁了呗!
因此,如果 name 没有索引,那么事务 C 会被阻塞,如果有索引,则不会被阻塞!
所以这里要注意,没索引的列不要轻易的锁,不要以为有行锁就可以为所欲为,并不是这样的。
间隙锁和临键锁
前面说了,记录锁需要加到记录上,但是如果要给此时还未存在的记录加锁怎么办?也就是要预防幻读的出现!
这时候间隙锁就派上用场了,它是给间隙加上锁。
比如此时有 1、3、5、10 这四条记录,之前的文章分析过,数据页中还有两条虚拟的记录,分别是 Infimum
和 Supremum
。
可以看到,记录之前都有间隙,那间隙锁呢,锁的就是这个间隙!
比如我把 3 和 5 之间的间隙锁了,此时要插入 id = 4 的记录,就会被这个间隙锁给阻塞了,这样就避免了幻读的产生!也就实现了锁定未插入的记录的需求!
还有个 Next-Key Locks
就是记录锁+间隙锁,像上面间隙锁的举例,只能锁定(3,5) 这个区间,而 Next-Key Locks
是一个前开后闭的区间(3,5],这样能防止查询 id=5 的这个幻读。
间隙锁之间不会冲突,间隙锁的唯一目的就是防止其他事务插入数据到间隙中 ,所以即使两个间隙锁要锁住相同的间隙也没有关系,因为它们的目的是一致的。
间隙锁可以显式禁用,它是在事务隔离级别为可重复读的时候生效的,如果将事务隔离级别更改为 READ COMMITTED,就会禁用了,此时,间隙锁对于搜索和索引扫描是禁用的,仅用于外键约束检查和重复键检查。
插入意向锁
插入意向锁,即 Insert Intention Locks,它也是一类间隙锁,但是它不是锁定间隙,而是等待某个间隙。比如上面举例的 id = 4 的那个事务 C ,由于被间隙锁给阻塞了,所以事务 C 会生成一个插入意向锁,表明等待这个间隙锁的释放。
并且插入意向锁之间不会阻塞,因为它们的目的也是只等待这个间隙被释放,所以插入意向锁之间没有冲突。
它的目的不在于锁定资源防止别人访问,我个人觉得更像是为了遵循 MySQL 的锁代码实现而为之。
锁其实就是内存里面的一个结构,每个事务为某个记录或者间隙上锁就是创建一个锁对象来争抢资源。
如果某个事务没有抢到资源,那也会生成一个锁对象,只是状态是等待的,而当拥有资源的事务释放锁之后,就会寻找正在等待当前资源的锁结构,然后选一个让它获得资源并唤醒对应的事务使之得以执行。
所以按照这么个逻辑,那些在等待间隙锁的插入事务,也需要对应的建立一个锁结构,然后锁类型是插入意向锁。
这样一来,间隙锁的事务在释放间隙锁的时候,才能得以找到那些等待插入的事务,然后进行唤醒,而由锁的类型也可以得知是插入意向锁,之间不需要阻塞,所以可以一起执行插入。
Auto-Inc Lock
Auto-Inc Lock 是一个特殊的表级锁,用于自增列插入数据时使用。 在插入一条数据的时候,需要在表上加个 Auto-Inc Lock,然后为自增列分配递增的值,在语句插入结束之后,再释放 Auto-Inc Lock。
在 MySQL 5.1.22 版本之后,又弄了个互斥量来进行自增减的累加。互斥量的性能高于 Auto-Inc Lock,因为 Auto-Inc Lock是语句插入完毕之后才释放锁,而互斥量是在语句插入的时候,获得递增值之后,就可以释放锁,所以性能更好。
但是我们还需要考虑主从的情况,由于并发插入的情况,基于 statement -based binlog 复制时,自增的值顺序无法把控,可能会导致主从数据不一致。
所以 MySQL 有个 innodb_autoinc_lock_mode 配置,一共有三个值:
- 0,只用 Auto-Inc Lock。
- 1,默认值,对于插入前已知插入行数的插入,用互斥量,对于插入前不知道具体插入数的插入,用 Auto-Inc Lock,这样即使基于 statement -based binlog 复制也是安全的。
- 2,只用互斥量。
扩展学习
还有一个谓词锁,Predicate Locks,这个比较少见,大家了解下就好了。
InnoDB 是支持空间数据的,所以有空间索引,为了处理涉及空间索引的操作的锁定,next-key locking 不好使,因为多维数据中没有绝对排序的概念,因此不清楚 “下一个” key 在哪。
所以为了支持具有空间索引的表的隔离级别,InnoDB 使用谓词锁。
空间索引包含最小边界矩形(MBR)值,因此 InnodB 通过在用于查询的 MBR 值上设置谓词锁定,使得 InnoDB 在索引上执行一致性读, 其他事务无法插入或修改与查询条件匹配的行。
最后
至此想必大家对 MySQL 的锁有了进一步理解吧!
最近鸭鸭整了一个✏️ 面试刷题神器面试鸭 ,已经有近 1500 道面试题目啦,欢迎大家来阅读!
我是鸭鸭,我们下期见~