0
0
0

MySQL :InnoDB 锁机制与死锁

2026-10-29
文章摘要
|

适用版本:MySQL 5.7 / InnoDB

一、为什么有 MVCC 还需要锁?

  • MVCC:主要负责普通快照读的数据版本可见性。

  • 锁机制:协调并发事务之间的读写冲突,保护记录与索引范围。

在 RC、RR 下,普通 SELECT 通常是快照读;SELECT ... FOR UPDATE、UPDATE、DELETE 等需要结合锁机制。

二、行级锁与 S/X 锁的区别

这是两个不同的分类维度。

分类

解决的问题

类型

锁定范围

锁住哪里?

记录锁、间隙锁、临键锁

锁模式

以什么方式加锁?

S 共享锁、X 排他锁

例如:

SELECT * FROM orders
WHERE id = 25
FOR UPDATE;

假设主键等值命中已有记录,在 RR 下通常是:

对 id=25 的索引记录加 X 模式的 Record Lock。

所以记录锁与排他锁并不矛盾,可以同时描述同一个锁。

InnoDB 的行级锁实际作用于索引记录及其相关间隙,不是直接锁住 Java 对象。

三、记录锁、间隙锁、临键锁

假设主键索引中存在:

20、25、30

锁类型

锁定范围

含义

Record Lock

25

锁住已有索引记录

Gap Lock

(20,25)

锁住 20 和 25 之间的间隙

Next-Key Lock

(20,25]

间隙 + 右边的索引记录 25

1. 为什么记录锁叫行级锁?

因为它的锁定粒度可以小到某条已有索引记录,而不是整张表。

例如 id=25 的 X 记录锁,通常会阻止其他事务更新这一条记录,但不会因为该锁本身阻止更新 id=30。

2. 为什么叫间隙锁?

间隙是索引中相邻两条记录之间的空位。

例如只有:

20、30

那么存在间隙:

(20,30)

如果 25 已经存在:

20、25、30

间隙就变成:

(20,25)
(25,30)

因为 25 把原来的间隙分成了两个部分。

注意:讨论的是同一个索引中的相邻索引项;其他索引可能具有不同的键值排列。

3. 纯间隙锁是否阻止 UPDATE?

纯 Gap Lock 主要阻止插入,不直接锁住已有记录。

假设事务 A 只持有 (20,25) 与 (25,30) 的纯间隙锁,不持有记录锁,也没有其他冲突。

事务 B 操作

是否受纯间隙锁阻塞

INSERT id=22

会

INSERT id=28

会

UPDATE id=25 的非索引字段

不会

DELETE id=25

不会仅因纯间隙锁而阻塞

如果 UPDATE 修改索引键,导致需要向被锁定的间隙插入新的索引项,则仍可能被阻塞。

记忆:纯间隙锁锁的是插入位置,不是已有记录。

4. 临键锁为什么包含右边记录?

在常见索引区间表示中:

Gap Lock:(20,25)
Record Lock:25

Next-Key Lock:(20,25]

因此:

Next-Key Lock = Gap Lock + Record Lock

左括号 ( 表示不包含 20,右方括号 ] 表示包含 25。

例如事务 A 持有 X 模式的 (20,25] 临键锁:

  • 插入 id=22:通常等待。

  • 更新 id=25:通常等待。

  • 更新 id=20:不因为这个临键锁本身等待。

注意:具体 SQL 获得的锁定范围由索引扫描决定,可能超出 WHERE 条件指定的边界。

四、共享锁 S 与排他锁 X

1. 共享锁

SELECT * FROM orders
WHERE id = 25
LOCK IN SHARE MODE;

允许不同事务对同一记录同时持有 S 锁。

2. 排他锁

SELECT * FROM orders
WHERE id = 25
FOR UPDATE;

对命中的记录申请 X 锁。

3. S/X 兼容性

已有锁 \ 请求锁

S

X

S

兼容

冲突

X

冲突

冲突

  • S + S:读读共享。

  • S + X:读写互斥。

  • X + X:写写互斥。

注意:这里讨论的是同一索引记录上的记录锁。纯 Gap Lock 的 S/X 锁可以相互兼容,不能照搬记录锁的兼容表。

五、MVCC、记录锁和间隙锁之间的关系

机制

主要作用

MVCC

判断普通快照读可见的数据版本

Record Lock

控制已有记录的并发访问与修改

Gap Lock

防止在受保护间隙内插入

Next-Key Lock

同时保护记录和前面的间隙

在 RR 下,InnoDB 利用 MVCC 处理普通快照读的可见性,利用记录锁、间隙锁和临键锁协调当前读、写操作及范围并发。

在 RC 下,普通搜索和索引扫描通常不使用间隙锁,但唯一键冲突检查、外键检查等仍可能使用。

六、MySQL 死锁

死锁:多个事务相互等待对方持有的资源,形成循环等待。

例如:

事务 A:锁住 id=1
事务 B:锁住 id=2

事务 A:等待 id=2
事务 B:等待 id=1

形成循环等待 → 死锁

死锁发生后

InnoDB 默认开启死锁检测,检测到死锁后:

  1. 分析锁等待关系。

  2. 选择一个事务作为牺牲者。

  3. 回滚该事务的全部操作,释放锁。

  4. 让其他事务继续执行。

常见错误码:1213。

死锁与锁等待超时

对比

死锁

锁等待超时

原因

循环等待

等待时间过长

错误码

1213

1205

InnoDB 默认处理

回滚牺牲事务

只回滚超时语句

锁等待超时也可能因为配置或 Spring 事务异常处理导致整个事务回滚。

排查命令

SHOW ENGINE INNODB STATUS\G

重点查看:

LATEST DETECTED DEADLOCK

记录所有检测到的死锁:

SET GLOBAL innodb_print_all_deadlocks = ON;

排查结束:

SET GLOBAL innodb_print_all_deadlocks = OFF;

七、生产环境优化建议

  1. 统一加锁顺序:多个事务按相同顺序访问资源,减少循环等待。

  2. 缩短事务时间:避免在事务中执行耗时 HTTP 请求。

  3. 合理设计索引:减少不必要的扫描和锁定范围。

  4. 正确处理重试:死锁发生后,应在新的事务中有限重试完整业务操作,并做好幂等保护。

八、面试重点

1. 行级锁和 X 锁有什么区别?

行级锁描述锁的粒度和范围,X 锁描述排他模式。可以同时存在,例如 X 模式的记录锁。

2. 为什么 25 已经存在时,间隙是 (20,25) 和 (25,30)?

因为间隙由索引中相邻记录之间的空位决定,25 的存在把原来的区间分成了两个间隙。

3. 纯 Gap Lock 能阻止更新 id=25 吗?

通常不能阻止更新已有记录的非索引字段。它主要阻止向受保护间隙插入索引记录。

4. Gap Lock 与 Next-Key Lock 的区别?

Gap Lock 只保护间隙,例如 (20,25);Next-Key Lock 同时保护间隙和右边索引记录,例如 (20,25]。

5. S 锁与 X 锁如何兼容?

同一记录上 S/S 兼容,S/X 和 X/X 冲突。

6. 死锁与普通锁等待的区别?

普通锁等待不一定形成循环;死锁存在循环等待,InnoDB 默认会自动检测并选择事务回滚。


九、最终速记

  • 记录锁:锁已有索引记录。

  • 间隙锁:阻止向相邻索引记录之间插入。

  • 临键锁:间隙锁 + 右侧记录锁。

  • S 锁:共享模式。

  • X 锁:排他模式。

  • 行级锁与 S/X 锁:分别描述范围和模式。

  • 死锁:事务循环等待,InnoDB 回滚牺牲事务。

最重要的一句话:

锁的范围决定锁住哪里,锁的模式决定哪些操作互斥;Record Lock 锁记录,Gap Lock 锁插入间隙,Next-Key Lock 同时锁住前面的间隙和右侧记录。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或者给予支持!

评论