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

相关推荐
川石课堂软件测试6 小时前
学习软件测试需掌握的一些知识
数据库·python·sql·功能测试·网络协议·mysql·http
2501_937767167 小时前
自建 SQL 数据库定时备份踩坑复盘与落地思路
数据库·sql
1024奇点14 小时前
【仓颉语言入门 · 第二篇 第 12 课】
开发语言·数据库·ide·mysql·开源
冰暮流星14 小时前
mysql之insert优化
数据库·mysql
北方的银狐-Zero14 小时前
OntoL 7.0|面向工业多智能体的语义本体套件,让异构系统真正 “看懂彼此”——OntoL的本体产品矩阵
人工智能·mysql·sqlite·1024程序员节·本体论·本体产品
瑞克-桑切斯15 小时前
4、MySQL 索引与事务
数据库·mysql
二宝哥15 小时前
【问题】本地启动springboot项目启动flowable引擎会提示数据库脚本错误,同事本地启动没有报错(MySQL表明大小写敏感参数)
数据库·mysql
l1t16 小时前
DeepSeek总结的ALTER FUNCTION SET work_mem:无需全局更改即可修复 PostgreSQL 磁盘溢出
sql·缓存·postgresql
瑞克-桑切斯16 小时前
2、 MySQL 主从复制与读写分离
数据库·mysql