全局锁
一般用于全库逻辑备份,以保证在备份期间不会出现数据或表结构的变更
表级锁
表锁
|
|
元数据锁
元数据锁(MDL)不需要显式的使用,我们对数据库表进行操作时,会自动加上 MDL
- 对一张表进行 CRUD 操作时,加的是 MDL 读锁
- 对一张表做表结构变更时,加的是 MDL 写锁
MDL 是为了保证用户对表执行 CRUD 操作时,不会有其他线程对这个表结构做变更
MDL 会在事务提交之后释放,在事务执行期间,MDL 是一直持有的
意向锁
- 在对某些记录加上共享锁之前,需要先在表级别加一个「意向共享锁」
- 在对某些记录加上独占锁之前,需要先在表级别加一个「意向独占锁」
在执行插入、更新、删除操作时,需要先对表加上「意向独占锁」,然后再对该记录加独占锁
普通的 select 语句是不会加行级锁的,是通过 MVCC 实现的一致性读,是无锁的
不过也可以通过以下方式加锁
|
|
意向共享锁和意象独占锁是表级锁,不会和行级的锁发生冲突,意向锁之间也不会发生冲突,只会和共享表锁和独占表锁发生冲突
如果没有意向锁,那么加独占表锁时,需要遍历表中所有记录查看是否存在独占锁。
有了意向锁,就可以直接在表级别判断是否有记录被加锁
AUTO-INC 锁
通常来说表中的主键都会设置为自增的,自增主要是通过 AUTO-INC 锁实现的
在插入数据时,会加一个表级别的 AUTO-INC 锁,然后为 AUTO_INCREMENT 的字段赋值在增值,在插入语句执行完成后,才会把锁释放掉
显然这样在高并发下有些低效,因此 InnoDB 还提供了一个轻量级的锁来解决这一点: 当系统变量 innodb_autoinc_lock_mode 的值为:
- 0: 使用 AUTO-INC 锁,语句执行完毕才释放锁
- 1:
- 普通 insert 语句:申请之后就释放
- 类似 insert … select 这样的批量插入数据的语句,还是要等到语句结束才会释放锁
- 2:采用轻量级锁,申请自增主键之后就释放锁
行级锁
首先需要明确共享锁(S锁)和独占锁(X 锁):

行级锁主要有三个类型:
- Record Lock: 记录锁,也就是仅仅把一条记录锁上
- Gap Lock: 间隙锁,锁定一个范围,不包含记录本身
- Next-Key Lock: 临键锁,以上两者的结合,锁定一个范围,并锁定记录本身
记录锁
Record Lock 称为记录锁,锁住的是一条记录
间隙锁
间隙锁只存在于可重复读隔离级别,是为了解决可重复读级别下幻读的问题
假设表中存在一个范围 id 为 (3,5) 的间隙锁,那么其他事务就无法插入 id = 4 的记录了
间隙锁虽然存在 S 锁和 X 锁两种类型,但并没有什么区别。
间隙锁之间是兼容的,两个事务可以同时持有包含共同间隙范围的间隙锁,不存在互斥关系。因为间隙锁的目的是防止插入幻影记录 而提出的
临键锁
临键锁是记录锁和间隙锁的结合,锁定一个范围和记录本身
假设表中存在一个范围 id 为 (3, 5] 的临键锁,那么其他事务既不能插入 id = 4 的记录,也不能修改 id = 5 的记录
前文提到间隙锁之间是互相兼容的,但由于临键锁包含记录锁,因此 X 类型的临键锁之间是不兼容的
插入意向锁
一个事务在插入记录时,需要判断插入位置是否已经被其他事务加了间隙锁
如果有,插入操作就会发生阻塞,直到间隙锁释放。在此期间,会生成一个插入意向锁,表明事务想在某个区间插入新纪录,但现在处于等待状态
例如,假设事务已经对表加了一个 (3,5) 的间隙锁,之后事务 B 向该表插入一条 id = 4 的记录。这时会判断此位置已经被事务 A 加了间隙锁,于是事务 B 会生成一个插入意向锁,然后置于等待状态。此时事务 B 就会发生阻塞,直到事务 A 提交了事务。
插入意向锁虽然名字叫意向锁,但其实是一种特殊的间隙锁,属于行级别锁
行级锁是如何加锁的
行级锁加锁的规则比较复杂,在不同场景下,加锁的形式是不同的
- 加锁的对象是索引
- 默认的加锁方式是临键锁
- 在使用记录锁或者间隙锁时就能避免幻读的场景下,临键锁会退化成记录锁或者间隙锁
假设存在以下表结构和数据
|
|

唯一索引等值查询
- 当记录存在时,在索引树上定位到这一条记录后,将该记录的索引中的临键锁退化为记录锁
- 当记录不存在时,在索引树上找到第一条大于该查询记录的记录后,将该记录的索引中的临键锁退化为间隙锁
我们可以通过 select * from performance_schema.data_locks\G; 语句来查看事务执行 SQL 过程中加了什么锁
语句执行后的 LOCK_TYPE 取值如下:
- LOCK_TYPE: ‘X’,说明是临键锁
- LOCK_TYPE: ‘X, REC_NOT_GAP’,说明是记录锁
- LOCK_TYPE: ‘X, GAP’,说明是间隙锁
记录存在时
|
|
那么此时事务会为 id=1 的这条记录加上 X 型的记录锁。接下来如果有其他事务对记录进行更新或者删除,这些操作都会被阻塞
记录不存在时
|
|
此时通过上述语句可以看到共加了两个锁:
- 表锁:X 类型的意向锁
- 行锁:X 类型的间隙锁

因此,此时事务在 id = 5 记录的主键索引加的是间隙锁 (1, 5)
唯一索引范围查询
范围查询与等值查询的枷锁规则是不同的。当唯一索引进行范围查询时,会对每一个扫描到的索引加临键锁,在遇到下面这些情况时,会退化成记录锁或间隙锁:
- 针对大于等于的情况,因为存在等值查询,如果等值查询的记录存在,那么该记录的临键锁会退化为记录锁
- 针对小于或者小于等于的范围查询:
- 当条件值的记录不存在表中,那么扫描到终止范围查询的记录时,该记录的临键锁会退化为间隙锁
- 当条件值的记录存在于表中:
- 如果是小于条件的范围查询,扫描到终止范围查询的记录时,该记录的临键锁会退化为间隙锁
- 如果小于等于的范围查询,扫描到终止范围查询的记录时,高架路的临键锁不会退化
详见 唯一索引范围查询