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 再重试;统一各事务的加锁顺序能少踩很多。

相关推荐
xcLeigh4 小时前
聊聊国产化替换:好用数据迁移工具KDMS怎么帮咱们搞定评估难
数据库·sql·数据迁移·kes·kdms
独泪了无痕6 小时前
SQL函数实战:GREATEST与LEAST的技巧
数据库·sql·mysql
麻辣布丁7 小时前
MySQL篇面试题模拟
mysql
这个DBA有点耶7 小时前
MySQL 8.0.20移除了Block Nested Loop,之前学的JOIN优化知识还适用吗?
数据库·mysql·架构
Databend9 小时前
Databend 原生数据血缘:追溯指标来源,检查变更影响
大数据·数据库·sql
镜舟科技9 小时前
Semantic View 技术解析(二):业务口径如何进入数据库执行路径
数据库·sql·agent
StarRocks_labs11 小时前
当大模型调用进入执行引擎:StarRocks AI Function 全新能力解析
数据库·starrocks·sql·ai·pipeline·数据处理·join
开心麻瓜11 小时前
一条 SQL 跑了 8 秒,优化到 200ms 的全过程
mysql
润乾软件11 小时前
SQLazy:找出含 5 个以上连续字母表顺序字母的字符串
sql·sqlazy
FYKJ_201012 小时前
【毕设分享】基于Web的校园兼职信息发布与申请系统07059
前端·vue.js·spring boot·mysql·typescript·spark·课程设计