MySQL锁
MySQL锁
S锁与X锁
-
共享锁,Shared Locks,简称S锁,在事务要读取一条记录时,要先获取到记录的S锁。
-
独占锁,Exclusive Locks,简称X锁,在事务要修改一条记录时,要先获取到记录的X锁。
S锁与X锁之间的兼容关系如下:
| 兼容性 | S锁 | X锁 |
|---|---|---|
| S锁 | 兼容 | 不兼容 |
| X锁 | 不兼容 | 不兼容 |
多粒度锁
全局锁
全局锁说的是数据库级别的,会对整个数据库加锁,不能再修改。(一般也不会去用)
表级锁
-
表级别S锁和X锁
若对表加了S锁,则整个表只能被读,不能被修改;若对表加了X锁,则不能被读,更不能被其他事务修改。
-
表级别意向锁
即IS锁和IX锁。当对记录加S锁或X锁前,会对表加上IS锁或IX锁,作用是快速判断表内有无记录加了S锁或X锁。(这样的话,在对表加S锁或X锁时,就不用去遍历表中每一条记录,看有没有行级别的S锁或X锁)
-
元数据锁(MDL)
元数据锁是用来一个事务中用来保护表的结构的,对表操作时会自动加上,防止当前线程对表进行CRUD时,别的线程修改了表结构。
-
AUTO-INC锁 或 轻量级锁
若表中某个字段为
AUTO_INCREMENT属性,则后续插入纪录时,可以不指定该列数据,系统自动自增,自增的原理如下(两种模式)。- 一是AUTO-INC锁。AUTO-INC锁会在执行insert语句时加上,insert语句执行完后锁释放,若其他线程也想insert,则会因为获取不到AUTO-INC锁而阻塞。
- 二是轻量级锁。每次insert时只获取一下轻量级锁,生成本次自增的值后,立刻释放掉这个轻量级锁,不用等到整个insert语句完成。
- 这两种模式通过
innodb_autoinc_lock_mode系统变量进行选择。(0:AUTO-INC锁,1:混合使用,2:轻量级锁)
行级锁
-
记录锁(Record Locks)
对单个记录加的锁,分为S锁和X锁。
-
间隙锁(Gap Locks)
首先明确:行级锁都是加载索引上的(InnoDB)。所以间隙锁才会有“范围”这一概念,对某个区间(左开右开区间)进行加间隙锁,防止有新记录在这个区间写入。
间隙锁的唯一作用就是为了防止幻读现象!故不区分S锁和X锁。
-
临键锁(Next-Key Locks)
Next-Key Locks = Record Locks + Gap Locks。是一个左开右闭区间,也区分S锁和X锁(因为记录锁区分)。
-
插入意向锁(Insert Intention Locks)
和间隙锁一块出现的,当某个区间加了间隙锁后,该区间的某个新数据想要插入,但无法插入,由于InnoDB是先生成锁结构再修改锁状态的,所以此时该记录会有一个等待状态的插入意向锁。(比较鸡肋的一个锁)
插入意向锁不是意向锁,他是一种特殊的间隙锁,锁的不是一个区间,而是一个点。
MySQL InnoDB 隔离级别与锁机制
- READ UNCOMMITTED (读未提交)
这是最低的隔离级别,不使用一致性读,通常用于高性能但数据准确性要求不高的场景。
| 操作类型 | 锁机制 | 目的/说明 |
|---|---|---|
普通 SELECT |
不加锁 | 直接读取数据的最新版本(包括未提交版本),不涉及 MVCC。 |
UPDATE/DELETE |
行级排他锁 (X Lock) | 必须加锁,以保证数据修改的原子性和互斥性。 |
- READ COMMITTED (读已提交)
此级别下,SELECT 使用 MVCC。
| 操作类型 | 锁机制 | 目的/说明 |
|---|---|---|
普通 SELECT |
不加锁 | 使用 MVCC,每次 SELECT 都生成新的 Read View,读取已提交版本。 |
SELECT ... FOR UPDATE/FOR SHARE |
行级锁 (X/S Lock) | 遵循标准锁规则,仅对满足条件的记录加锁。不加间隙锁。 |
UPDATE/DELETE |
行级排他锁 (X Lock) | 仅对满足条件的记录加 X 锁,不加间隙锁。 |
- REPEATABLE READ (可重复读)
这是 InnoDB 的默认隔离级别。它使用 MVCC 保证读一致性,并使用 Next-Key Locks 解决幻读。
| 操作类型 | 锁机制 | 目的/说明 |
|---|---|---|
普通 SELECT |
不加锁 | 使用 MVCC,事务生命周期内使用首次生成的 Read View。 |
SELECT ... FOR UPDATE/FOR SHARE |
Next-Key Locks (NK 锁) | 这是关键!对于范围查询和非唯一索引查询,InnoDB 会加 NK 锁(间隙锁 + 记录锁)来锁定索引范围,防止幻读。 |
UPDATE/DELETE |
Next-Key Locks (NK 锁) | 对于范围查询和非唯一索引查询,加 NK 锁。对于精确主键/唯一键查询,退化为行级锁 (X Lock)。 |
Next-Key Lock 机制:
- 目的: 阻止其他事务在锁定的间隙中插入新记录。
- 实现: Next-Key Lock = 间隙锁 (Gap Lock) + 记录锁 (Record Lock)。
- SERIALIZABLE (串行化)
这是最高的隔离级别,主要依赖悲观锁。
| 操作类型 | 锁机制 | 目的/说明 |
|---|---|---|
所有 SELECT |
隐式 SELECT ... FOR SHARE |
所有普通读操作都被强制转换为共享锁读,对读取的行加 S 锁。 |
UPDATE/DELETE |
行级排他锁 (X Lock) | 遵循标准锁规则。 |
| 范围查询 | Next-Key Locks (NK 锁) | 和 REPEATABLE READ 类似,为了保证并发控制的严谨性,也会使用 NK 锁来锁定范围。 |
MySQL是如何加行级锁的?
分很多情况(规则性的东西,不用死记,重在理解就行):
- 唯一索引(主键索引)等值查询
| 场景 | 查找过程 | 施加的锁类型 | 锁模式 |
|---|---|---|---|
| 记录存在 | 定位到唯一记录 | 行级记录锁 (Record Lock) | $X$ 或 $S$ |
| 记录不存在 | 查找相邻记录,发现间隙 $(K_{i-1}, K_i)$ | 间隙锁 (Gap Lock) | $X$ 或 $S$ |
- 非唯一索引等值查询
| 场景 | 查找过程 | 施加的锁类型 | 锁模式 |
|---|---|---|---|
| 记录存在 | 找到所有满足条件的记录 | 1. 对所有匹配记录加 行级记录锁 (Record Lock)。2. 对该记录的间隙加 Next-Key Lock (NK 锁)。 | $X$ 或 $S$ |
| 记录不存在 | 查找相邻记录,发现间隙 $(K_{i-1}, K_i)$ | 间隙锁 (Gap Lock) | $X$ 或 $S$ |
- 非唯一索引范围查询
(适用于 WHERE id > 10 或 WHERE id BETWEEN 5 AND 15 等情况)
| 场景 | 查找过程 | 施加的锁类型 | 锁模式 |
|---|---|---|---|
| 记录存在 | 从起始点到扫描结束点 | 对所有扫描到的索引记录及它们之前的间隙加 Next-Key Lock (NK 锁)。注意: 范围结束点的锁是 NK 锁,而非单纯的 Record Lock。 | $X$ 或 $S$ |
| 记录不存在 | 扫描范围内的间隙 | 对扫描范围内的所有间隙加 Next-Key Lock (NK 锁)。 | $X$ 或 $S$ |
- 没加索引的查询 (全表扫描)
(适用于 WHERE 子句中使用的列没有索引的情况)
| 场景 | 查找过程 | 施加的锁类型 | 锁模式 |
|---|---|---|---|
| 记录存在 | 全表扫描,找到满足条件的记录 | 1. 对所有扫描到的记录(无论是否满足条件)都加 Next-Key Lock (NK 锁)。2. 最终的行级锁只保留在满足条件的记录上。 | $X$ 或 $S$ |
| 记录不存在 | 全表扫描 | 对所有扫描到的记录加 Next-Key Lock (NK 锁)。 | $X$ 或 $S$ |
死锁问题
这一部分偏实践,跟实际开发问题相关,需要用到前面的很多知识(需要知道有哪些锁的类型、要知道什么语句会使用什么锁、线上怎么看日志等),所以不多介绍(现在就暂时简单有个印象)。以后遇到问题了再来查询资料。
资料:
- [ ] 实际遇到问题记录如下:







