深浅主题
MySQL 死锁排查
问题现象
出现 Deadlock found when trying to get lock,某个事务被回滚;重试偶尔成功。它不同于仅等待过久的锁超时。
适用环境
MySQL 8 InnoDB;多事务交叉更新。
本文是可复用的排障示例,不代表某次个人生产事故。命令中的名称、账号和数据均为示例,执行前请替换为已确认的目标。
复现或诊断步骤
- 故障后及时读取 SHOW ENGINE INNODB STATUS 中最近一次死锁信息,注意文本可能包含业务数据。
- 关联两个事务的 SQL 顺序、索引和锁记录,不只看报错的最后一条语句。
- 在测试库用两个会话按相反顺序更新同一组测试行复现。
原因分析
事务 A 持有记录 1 等待记录 2,事务 B 持有记录 2 等待记录 1,形成等待环。范围扫描、外键检查和不合适索引也可能扩大锁集合。InnoDB 通常会选择受害事务回滚。
修复示例
sql
-- 两个会话均按固定主键顺序锁定,避免相反顺序
START TRANSACTION;
SELECT id FROM account
WHERE id IN (1001, 1002)
ORDER BY id FOR UPDATE;
UPDATE account SET balance = balance - 10 WHERE id = 1001;
UPDATE account SET balance = balance + 10 WHERE id = 1002;
COMMIT;
-- 仅使用已备份、可恢复的测试账户演练统一事务访问顺序并缩短持锁时间,确保过滤条件使用合理索引。应用对死锁做有限次数、带抖动的整事务重试;所有外部副作用需幂等。回滚事务后再重试,不能只重放最后一条 SQL。
验证方法
以同样并发模型重放,统计死锁率、重试次数和最终余额不变量。故障时主动终止测试会话应先 ROLLBACK;不要在真实资金账户上演练。
常见误区
提高 lock_wait_timeout 不能解除循环等待。死锁不总是数据库缺陷,降低隔离级别也不应作为未经一致性评估的快速修复。