MySQL InnoDB 行锁与死锁排查实战演示
💡 导语 :
在高并发环境下,MySQL InnoDB 存储引擎的行锁与唯一索引机制经常成为性能瓶颈。
本文通过一个可复现的示例,系统演示如何分析 事务锁等待 与 死锁问题 ,并利用
performance_schema.data_locks精确定位锁冲突来源。阅读本文,你将掌握:
- InnoDB 行锁、间隙锁、插入意向锁的原理
- 死锁形成过程及排查思路
performance_schema的实战使用方法
目录
-
[一、建表 SQL](#一、建表 SQL)
-
- [1. 事务 A 插入](#1. 事务 A 插入)
- [2. 事务 B 插入](#2. 事务 B 插入)
- [3. 事务 C 插入](#3. 事务 C 插入)
- [4. 事务 A 回滚](#4. 事务 A 回滚)
- [5. 事务 B 执行插入](#5. 事务 B 执行插入)
- [6. 事务 C 唤醒并触发死锁](#6. 事务 C 唤醒并触发死锁)
零:环境信息
mysql版本:8.0.32
innodb引擎
一、建表 SQL
sql
CREATE DATABASE IF NOT EXISTS demo;
USE demo;
DROP TABLE IF EXISTS orders;
CREATE TABLE orders (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
order_no VARCHAR(20) NOT NULL UNIQUE,
user_id BIGINT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
说明:
order_no为唯一索引字段,用于演示唯一约束下的锁行为- 使用
InnoDB引擎 - 默认事务隔离级别为
REPEATABLE READ
二、事务演示场景
我们模拟三个事务 A、B、C,均尝试插入同一个唯一索引值 'A10004':
| 事务 | 操作 | 状态 |
|---|---|---|
| A | 开启事务并插入 'A10004',不提交 |
持有锁 |
| B | 开启事务插入 'A10004' |
阻塞等待 A |
| C | 开启事务插入 'A10004' |
阻塞等待 B |
| A | 回滚事务 | 唤醒 B |
| B | 插入成功后提交 | C 被检测为死锁报错 |
三、执行流程与锁分析
1. 事务 A 插入
BEGIN;
INSERT INTO orders(order_no, user_id, amount) VALUES ('A10004', 1, 100.00);
锁行为:
- 对唯一索引
'A10004'加X(排他)锁 - 对表加
IX(意向排他)锁 - 状态:锁获取成功,但事务未提交
2. 事务 B 插入
BEGIN;
INSERT INTO orders(order_no, user_id, amount) VALUES ('A10004', 2, 100.00);
锁行为:
- 尝试加
S(共享)锁检查'A10004'是否存在 'A10004'被事务 A 持有X锁,B 阻塞- 此时 B 尚未进入插入阶段(未申请
X、INSERT_INTENTION锁)
3. 事务 C 插入
BEGIN;
INSERT INTO orders(order_no, user_id, amount) VALUES ('A10004', 3, 100.00);
锁行为:
- 同样尝试加
S锁检查唯一性 'A10004'被 A 阻塞- 队列等待顺序:B → C
4. 事务 A 回滚
ROLLBACK;
行为:
- 释放
'A10004'上的X锁 - 唤醒等待队列
- B 被唤醒执行,C 继续等待
5. 事务 B 执行插入
执行步骤:
- 唤醒后加
S锁检查唯一性 - 发现
'A10004'不存在 - 申请
X、INSERT_INTENTION锁 - 执行插入并升级为
X锁 - 插入成功,锁保持至提交或回滚
6. 事务 C 唤醒并触发死锁
分析过程:
- 唤醒后尝试加
S锁检查唯一性 'A10004'已被 B 插入并持有X、INSERT_INTENTION锁- C 需要等待 B 的锁,形成循环等待
- InnoDB 死锁检测机制触发,C 被回滚
报错信息:
ERROR 1213 (Deadlock): Deadlock found when trying to get lock; try restarting transaction
四、锁类型解析
| 锁类型 | 含义 | 作用 |
|---|---|---|
X |
排他锁 | 禁止其他事务对相同索引记录加 X 或 S 锁 |
S |
共享锁 | 允许其他事务加 S 锁,但阻止 X 锁 |
X, INSERT_INTENTION |
插入意向锁 | 对间隙加锁,保证插入唯一值时的顺序性 |
S, GAP |
共享间隙锁 | 锁定索引间隙,防止其他事务插入相同值,但允许读取 |
五、排查技巧
- 查看当前事务
SELECT * FROM information_schema.innodb_trx; - 查看锁信息
SELECT * FROM performance_schema.data_locks\G;
关键字段说明:
| 字段 | 含义 |
|---|---|
|LOCK_MODE| 锁类型(X / S / GAP 等) |
|LOCK_STATUS| 锁状态:GRANTED(已获锁)或WAITING(等待中) |
|OBJECT_INSTANCE_BEGIN| 锁对象地址,可用于定位索引记录 |
|THREAD_ID| 对应会话线程 ID | - 常见问题判断
| 现象 | 排查思路 |
|---|---|
| 锁等待 | 查看LOCK_STATUS=WAITING的记录 |
| 死锁 | 查看SHOW ENGINE INNODB STATUS或错误日志中的ERROR 1213|
| 超时 | 检查innodb_lock_wait_timeout和ERROR 1205日志 |
六、总结
-
InnoDB 唯一索引插入流程:
- 加
S锁检查唯一性 - 申请
X、INSERT_INTENTION锁 - 最终升级为
X锁
- 加
-
锁等待顺序:
- InnoDB 遵循 FIFO 队列
- 前一个事务释放锁后,等待事务依次被唤醒
-
死锁检测机制:
- 当多个事务互相等待彼此持有的锁时,InnoDB 会主动检测并回滚其中一个事务以打破死锁
-
实践建议:
- 使用
performance_schema.data_locks精确分析锁类型与等待对象 - 避免长事务或高并发下重复插入相同唯一键
- 通过减少唯一索引竞争、控制事务粒度来降低死锁风险
- 使用
✍️ 结语:
死锁是并发系统中无法完全避免的现象,但通过合理的事务控制与锁分析手段,可以让我们快速定位并解决问题。 如果你在实际项目中遇到复杂锁等待场景,不妨打开 performance_schema.data_locks 查看锁信息,很多答案都在那儿。