死锁排查
约 1102 字大约 4 分钟
布欧-Lewyon
2026-05-15
首页 › MySQL › 事务与锁(在新窗口打开) › 死锁排查
死锁(Deadlock)是指两个或多个事务互相持有对方需要的锁,导致彼此都无法继续执行。InnoDB 会自动检测死锁并回滚其中一个事务(代价较小的事务),让另一个继续。
典型死锁场景
场景一:相互等待
-- 事务 A | 事务 B
START TRANSACTION; | START TRANSACTION;
|
UPDATE account SET balance=800 | UPDATE account SET balance=1200
WHERE id = 1; -- 持有 id=1 的 X 锁 | WHERE id = 2; -- 持有 id=2 的 X 锁
|
UPDATE account SET balance=900 | UPDATE account SET balance=1100
WHERE id = 2; -- 等待 B 释放 id=2 | WHERE id = 1; -- 等待 A 释放 id=1
|
-- Deadlock!InnoDB 选择回滚其中一个 |如何避免:所有事务按相同顺序访问资源:
-- 约定:总是先更新 id 小的
START TRANSACTION;
UPDATE account SET balance=800 WHERE id = 1; -- 总是先 id=1
UPDATE account SET balance=1200 WHERE id = 2; -- 再 id=2
COMMIT;场景二:间隙锁冲突
-- 数据: id 为 1, 4, 7
-- 事务 A | 事务 B
START TRANSACTION; | START TRANSACTION;
|
SELECT * FROM account | SELECT * FROM account
WHERE id BETWEEN 1 AND 7 | WHERE id BETWEEN 1 AND 7
FOR UPDATE; | FOR UPDATE;
-- 获得 (1,4] (4,7] 的 Next-Key Lock | -- 等待 A 释放间隙锁
|
INSERT INTO account VALUES (3, 'X'); | -- A 持有间隙锁,B 等待
-- 插入成功,获得 id=3 的锁 |
COMMIT; | -- A 释放锁,B 获得锁
| INSERT INTO account VALUES (5, 'Y');
| -- 成功场景三:外键/二级索引冲突
当表有外键或二级索引时,更新父表会引起子表加锁检查,容易引发死锁。
查看死锁信息
SHOW ENGINE INNODB STATUS
SHOW ENGINE INNODB STATUS\G在输出中搜索 LATEST DETECTED DEADLOCK 段落:
------------------------
LATEST DETECTED DEADLOCK
------------------------
2026-05-15 10:30:00 0x7f1234
*** (1) TRANSACTION:
TRANSACTION 12345, ACTIVE 5 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 100, OS thread handle 1234, query id 5678 localhost root updating
UPDATE account SET balance = 900 WHERE id = 2
*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 10 page no 3 n bits 72 index PRIMARY of table `demo`.`account` trx id 12345 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: ...
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 10 page no 3 n bits 72 index PRIMARY of table `demo`.`account` trx id 12345 lock_mode X locks rec but not gap waiting
Record lock, heap no 3 PHYSICAL RECORD: ...
*** (2) TRANSACTION:
TRANSACTION 12346, ACTIVE 3 sec starting index read
mysql tables in use 1, locked 1
2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 101, OS thread handle 5678, query id 5679 localhost root updating
UPDATE account SET balance = 1100 WHERE id = 1
*** (2) HOLDS THE LOCK(S): (锁 id=2)
*** (2) WAITING FOR THIS LOCK TO BE GRANTED: (等 id=1)
*** WE ROLL BACK TRANSACTION (1)关键信息:
- 哪两个事务在争
- 每个事务持有哪些锁
- 每个事务在等什么锁
- 谁被回滚了
information_schema 查看事务
-- 查看当前运行的事务
SELECT * FROM information_schema.INNODB_TRX\G
-- 查看锁等待情况
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
-- 查看所有锁(8.0 用 performance_schema)
SELECT * FROM performance_schema.data_locks;监控长事务
-- 找出运行超过 10 秒的事务
SELECT trx_id, trx_state, trx_started,
TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS seconds_running,
trx_mysql_thread_id
FROM information_schema.INNODB_TRX
WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 10;常见死锁模式与预防
| 模式 | 预防措施 |
|---|---|
| 资源访问顺序不一致 | 约定全局一致的资源访问顺序 |
| 间隙锁争用 | RR 级别下考虑降级为 RC(如果业务允许) |
SELECT ... FOR UPDATE 范围太大 | 缩小锁定范围(更精确的 WHERE) |
| 外键/索引导致隐式加锁 | 考虑减少外键依赖;合理设计索引 |
| 长事务 | 保持事务短小,及时 COMMIT |
降级隔离级别
-- 如果业务允许不可重复读,将隔离级别从 RR 降为 RC
-- RC 级别不使用 Gap Lock,发生死锁的概率大幅降低
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;设置超时时间
-- 设置锁等待超时(秒),避免无限等待
SET GLOBAL innodb_lock_wait_timeout = 10; -- 默认 50s
-- 设置死锁检测
SET GLOBAL innodb_deadlock_detect = ON; -- 默认开启在极高并发场景下,可选择关闭死锁检测(
innodb_deadlock_detect = OFF)并用innodb_lock_wait_timeout兜底,以节省死锁检测开销。
小结
- 死锁是两个事务互相等待对方释放锁,InnoDB 自动检测并回滚代价较小的事务。
SHOW ENGINE INNODB STATUS中LATEST DETECTED DEADLOCK提供详细死锁信息。information_schema.INNODB_TRX和performance_schema.data_locks可用于手动排查锁等待。- 预防死锁:固定资源访问顺序、缩小锁定范围、短事务、合理设置隔离级别。
