最后更新:2026-10-29
适用场景:MySQL 8.0/8.4、InnoDB、错误 1213 与事务并发更新
应用偶尔抛出 Deadlock found when trying to get lock,重试后又成功,很容易让人把它当成数据库抖了一下。但如果同一组操作反复发生死锁,还是要把事务持有什么锁、接着又在等什么锁画清楚。只盯着最后报错的那条 SQL,通常看不完整。
先分清 1213 和 1205
1213 是死锁错误;启用死锁检测时,InnoDB 会选一个事务回滚,让其他事务继续。1205 则是锁等待超时,两者的回滚范围不应混为一谈。
按 MySQL 的默认行为,死锁会回滚整个事务;锁等待超时通常只回滚当前语句,是否回滚整个事务受相关配置影响。应用不能看到异常就默认“前面的 SQL 也全部撤销了”。InnoDB 错误处理文档明确区分了这些情况。
记录错误码、事务边界、业务请求 ID 和失败时刻,再去取数据库侧证据。Spring 项目还要确认异常最终有没有传出事务边界,相关检查见事务不回滚怎么排查。
尽快保存最近一次死锁信息
在有相应权限的诊断会话中执行:
SHOW ENGINE INNODB STATUS;
使用 mysql 命令行时,可以用下面的写法方便阅读:
SHOW ENGINE INNODB STATUS\G
重点找 LATEST DETECTED DEADLOCK。这通常是最近一次被记录的死锁,不是完整历史;新的死锁可能把你要看的那次覆盖掉。SHOW ENGINE 需要相应权限,MySQL 文档列出的要求包括 PROCESS,没有权限就使用托管平台的诊断入口或由数据库管理员协助,不要给业务账号随手加管理权限。
先保存原始信息,再抽出几件事:每个事务正在执行的 SQL、已经持有的锁、正在请求的锁、涉及哪个表与索引,以及哪一方被回滚。报告里展示的当前语句,不代表这个事务此前只执行过这一句;还要用应用日志把事务内更早的操作补回来。
如果死锁频繁到无法及时抓取,可按现有变更流程评估临时开启 innodb_print_all_deadlocks,从错误日志持续收集,结束后恢复原配置。不要把含业务参数的完整数据库日志公开粘贴。
用两个会话理解“反向拿锁”
下面的例子只放在隔离测试库中操作,两个会话必须连接同一个测试库。先单独初始化测试表,表名已存在时先检查其用途,不要直接覆盖:
CREATE TABLE ops_deadlock_demo (
id INT PRIMARY KEY,
amount INT NOT NULL
) ENGINE=InnoDB;
INSERT INTO ops_deadlock_demo (id, amount) VALUES (1, 100), (2, 100);
会话 A 先执行:
START TRANSACTION;
UPDATE ops_deadlock_demo SET amount = amount + 1 WHERE id = 1;
会话 B 再执行:
START TRANSACTION;
UPDATE ops_deadlock_demo SET amount = amount + 1 WHERE id = 2;
保持两个事务都没提交。接着让 A 更新 id=2,它会等待 B;再让 B 更新 id=1:
-- 在会话 A 执行,等待锁
UPDATE ops_deadlock_demo SET amount = amount + 1 WHERE id = 2;
-- A 已经开始等待后,在会话 B 执行
UPDATE ops_deadlock_demo SET amount = amount + 1 WHERE id = 1;
死锁检测启用时,其中一个事务会被选为回滚对象,不要依赖一定回滚 A 或一定回滚 B。观察完成后,在两个测试会话中都执行 ROLLBACK;,释放仍然保留的测试事务。
这个例子要说明的是顺序:A 先拿 1 再等 2,B 先拿 2 再等 1。真实业务可能涉及二级索引、范围锁或唯一键检查,不能把所有死锁都简化为两条主键 UPDATE,但排查时同样要重建“持有 → 等待”的环。
先改事务结构,再设计重试
如果多条业务路径会更新同一批记录,尽量统一访问顺序,并缩短事务持续时间。数据库事务里夹着外部 HTTP 调用或大段无关计算,会让锁被持有得更久。还应核对 WHERE 条件和实际使用的索引,避免一次操作触及远超预期的范围。
遇到死锁,应在符合业务幂等要求的前提下,重试整个事务,而不是只重试最后失败的 SQL。Spring 项目应让每次尝试进入一个新的、清晰的事务边界;不要在已经失败的同一个事务里吞掉异常后继续提交。
重试还要有次数上限、退避与最终失败处理。事务之外的扣款、发消息等副作用不能跟着无条件重做。高并发下所有请求一起立刻重试,也可能把同一组热点记录再次挤满。MySQL 死锁处理建议介绍了事务顺序、时长和重试的处理方向。
最后检查是否只是“换了一个失败方式”
修复后,使用接近原场景的并发更新验证:死锁次数是否下降、事务耗时和锁等待是否改善、最终业务结果是否正确。不能只看应用不再抛 1213,就认定问题消失;它也可能变成了更长的等待,最后报 1205。
如果大量请求在等连接,应继续看数据库连接池耗尽排查。把连接池调大,并不会自动拆开数据库里的等待环,反而可能让更多事务同时参与竞争。
参考资料
MySQL 8.4:InnoDB 死锁
MySQL 8.4:减少与处理死锁
MySQL 8.4:InnoDB 错误处理
MySQL 8.4:SHOW ENGINE
更新记录
2026-10-29:创建文章,整理死锁取证、双会话示例及事务重试边界。
评论