MySQL 死锁怎么排查:把 innodb status 里的 HOLDS 和 WAITING 对上

上周有同学甩过来一条报错,就一行:

vbnet 复制代码
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

面试里这题经常被答成「四个必要条件」。线上真正要做的是读日志。InnoDB 默认会做死锁检测,挑一个事务回滚。应用侧就算 SQL 逻辑没写错,也得能重试。这是 MySQL 8.4 手册里写明的。

下面用两行账户表,把交叉加锁跑一遍。环境是 MySQL 8.4.11,innodb_deadlock_detect=ON。

sql 复制代码
CREATE TABLE account (
  id INT PRIMARY KEY,
  balance INT NOT NULL
) ENGINE=InnoDB;

INSERT INTO account VALUES (1, 100), (2, 200);

两个会话同时开事务:

sql 复制代码
-- 会话 A
BEGIN;
UPDATE account SET balance = balance - 10 WHERE id = 1;
-- 隔三秒
UPDATE account SET balance = balance + 10 WHERE id = 2;

-- 会话 B
BEGIN;
UPDATE account SET balance = balance - 10 WHERE id = 2;
-- 隔一秒
UPDATE account SET balance = balance + 10 WHERE id = 1;

我这次 A 被回滚,报了 1213。B 提交成功,表变成 (1, 110)、(2, 190)。

然后跑:

sql 复制代码
SHOW ENGINE INNODB STATUS\G

翻到 LATEST DETECTED DEADLOCK。我这次现场是这样的(物理记录只留关键行):

perl 复制代码
*** (1) TRANSACTION:
TRANSACTION 1815, ACTIVE 3 sec starting index read
UPDATE account SET balance = balance + 10 WHERE id = 1

*** (1) HOLDS THE LOCK(S):
RECORD LOCKS ... index PRIMARY of table `demo`.`account`
trx id 1815 lock_mode X locks rec but not gap
Record lock, heap no 3  ... hex 80000002

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
... lock_mode X locks rec but not gap waiting
Record lock, heap no 2  ... hex 80000001

*** (2) TRANSACTION:
TRANSACTION 1814, ACTIVE 3 sec starting index read
UPDATE account SET balance = balance + 10 WHERE id = 2

*** (2) HOLDS THE LOCK(S):
... heap no 2  ... hex 80000001

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
... heap no 3  ... hex 80000002 waiting

*** WE ROLL BACK TRANSACTION (2)

读法就三处:

HOLDS 是已经拿到的锁。(1) 拿着 hex 80000002,InnoDB 把有符号整数的最高位翻掉存,所以这是 id=2。它正在等 80000001,也就是 id=1。

(2) 正好反过来:拿着 id=1,等 id=2。环对上了。

最后一行 WE ROLL BACK TRANSACTION (2) 对上了会话 A 的 1213。InnoDB 会挑「更小」的事务回滚,大小按插入、更新、删除的行数算。两边都只改过一行,它回滚了标成 (2) 的那个。

lock_mode X locks rec but not gap 是排他记录锁,没有间隙。这次是主键等值更新,跟可重复读里的间隙锁不是一回事。别一上来把死锁甩给幻读。

我后来把这段日志当口述提纲,在面灵里过了两遍。面试官能听懂的版本其实就这三句:谁 HOLDS、谁 WAITING、谁被回滚。

还有两个容易背错的点。

很多笔记还在写 information_schema.INNODB_LOCKS。MySQL 8.0 起这张表没了。活着的锁看 performance_schema.data_locks。死锁现场本身还是 status 这段。

而且 SHOW ENGINE INNODB STATUS 只留最近一次。默认 innodb_print_all_deadlocks=OFF。死锁如果一阵一阵来,要把这个打开,写到错误日志里,不然下一次会把上一次盖掉。

排查讲到这里就够了。交叉更新顺序会成环;InnoDB 会回滚其中一个;应用必须接住 1213 再重试;统一各事务的加锁顺序能少踩很多。

相关推荐
刘胡子大叔8 小时前
MySQL 升到 8.4 前的兼容性自检清单
mysql
for_ever_love__9 小时前
MySQL 主从复制讲透:binlog 原理、搭建步骤与读写分离
java·数据库·mysql·binlog·读写分离·原理·主从复制
fish_xk13 小时前
mysql中的表的约束
数据库·mysql
java1234_小锋15 小时前
【技术专题】Mysql8 数据库 - Mysql8 添加,更新,删除数据
数据库·mysql
冰暮流星17 小时前
mysql之索引创建,查看,删除
数据库·mysql
福兮说19 小时前
IP 地址转整数的七个坑:192.168.1.1 算出负数、127.1 也是合法地址、存进 INT 直接溢出
javascript·网络·网络协议·tcp/ip·mysql·golang
2501_9336707920 小时前
2027届数学与应用数学转经营分析:SQL、财务指标与业务指标补齐路线
数据库·sql·数学建模
Q264336502321 小时前
【有源码】基于Spring Boot和Vue的在线考试与交流系统-教育信息化背景下在线考试与交流一体化平台
java·vue.js·spring boot·mysql·spring·毕业设计·课程设计
都适、隶仁ミ1 天前
【等保加固】MySQL5.7.38
运维·服务器·mysql·安全·等保
fengkai45451 天前
十一、MySQL 第 4-7 章
运维·数据库·mysql