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

相关推荐
深念Y30 分钟前
数据库层设计的取舍:ORM 便利性与手写 SQL 的安全性权衡
java·数据库·sql·golang·框架·语言·ome
clorinda34 分钟前
SQL 快速入门:题目单知识点精炼总结
java·数据库·sql
zgl_200537791 小时前
源代码:跨数据库通用“字段级”数据血缘解析与图形化(2/3:标注信息的拆解、检验、保存)
大数据·数据库·数据仓库·sql·数据挖掘·etl·嵌入式实时数据库
树下水月1 小时前
python3 使用canal监听后,kafka数据同步从mysql到clickhouse数据库 完整复盘
数据库·mysql·kafka
东风破_2 小时前
博客数据库进阶设计:点赞、收藏、评论、标签与文件表怎么建?
后端·mysql
迷枫7123 小时前
SQL性能排查记录
java·数据库·sql
东风破_11 小时前
从输入 juejin.cn 到看到页面:DNS、Nginx、服务器集群与 CDN 到底在做什么?
后端·mysql
小成很成11 小时前
mysql 8.0.21 升级 mysql 8.0.45(经过生产环境验证)
android·mysql·adb
东风破_11 小时前
从 0 设计一个博客数据库:用户、头像与文章表应该怎么建?
后端·mysql