死锁本质是循环等待锁,即两个及以上事务各自持有一把锁又同时申请对方持有的锁,形成闭环;单事务、表级锁不会死锁,高发于InnoDB行锁+多语句事务,主因是加锁顺序不一致。

死锁本质是循环等待锁,不是“锁太多”
MySQL死锁不是因为并发高或锁得狠,而是两个及以上事务**各自持有一把锁,又同时申请对方刚持有的那把锁**,形成闭环。InnoDB 检测到这个环,就随机选一个事务回滚,并抛出 ERROR 1213 (40001): Deadlock found when trying to get lock。关键点在于:单个事务不会死锁;表级锁(如 MyISAM)也不会死锁;真正高发场景只在 InnoDB 行锁 + 多语句事务中。
最常见触发场景:加锁顺序不一致
这是生产环境 80% 以上死锁的根源。比如两个事务更新同一组主键,但顺序相反:
-- 事务A(按 id 升序执行) START TRANSACTION; SELECT * FROM orders WHERE id IN (101, 205) FOR UPDATE; UPDATE orders SET status = 'paid' WHERE id = 101; UPDATE orders SET status = 'paid' WHERE id = 205; COMMIT;
-- 事务B(按 id 降序执行,或因应用层随机取数导致) START TRANSACTION; SELECT * FROM orders WHERE id IN (205, 101) FOR UPDATE; -- ⚠️ 这里先锁 205 UPDATE orders SET status = 'shipped' WHERE id = 205; UPDATE orders SET status = 'shipped' WHERE id = 101; -- 此时需等 A 释放 101 → A 又在等 B 释放 205 → 死锁
-
IN子句中的值顺序会影响加锁顺序 —— MySQL 默认按主键值升序加锁,所以WHERE id IN (205, 101)实际仍先锁101;但如果用非主键字段(如status)且无索引,就可能全表扫描、锁序不可控 - 应用层若用
ORDER BY RAND()或乱序拼接 ID 列表,极易制造不一致锁序 - 跨多张表操作时(如先更新
user再更新account),若不同事务调用链路顺序颠倒,也会触发
容易被忽略的“隐形锁”:间隙锁与 next-key 锁
在可重复读(RR)隔离级别下,InnoDB 对范围查询(BETWEEN、>=、LIKE 'abc%')默认使用 next-key lock(行锁 + 间隙锁),它会锁住记录本身和“前一个间隙”,导致看似不相关的行也被阻塞。
例如:
CREATE TABLE t (id INT PRIMARY KEY, name VARCHAR(10)); INSERT INTO t VALUES (1,'a'), (5,'b'), (10,'c');
此时执行:
-- 事务A SELECT * FROM t WHERE id > 5 AND id < 10 FOR UPDATE;
会锁住间隙 (5,10) —— 即使表中没有 id=7 的记录,事务B执行 INSERT INTO t VALUES (7,'x') 也会被阻塞。如果事务B同时持有 id=1 的行锁并试图锁 (5,10),而事务A又反过来想锁 id=1,死锁就成立了。
- 唯一索引上的等值查询(
WHERE id = ?)只加record lock,不加间隙锁;但普通索引或范围查询几乎必带间隙锁 -
INSERT ... SELECT、UPDATE ... JOIN等语句隐式扫描范围时,也容易意外引入间隙锁 - 降低隔离级别到读已提交(RC)可禁用间隙锁,但会带来幻读 —— 需权衡业务是否容忍
怎么快速定位和验证死锁原因?
别只看报错,要抓现场。InnoDB 每次死锁后只保留最后一次的详细信息,必须立刻查:
SHOW ENGINE INNODB STATUS\G
输出中重点关注 LATEST DETECTED DEADLOCK 区块,它会明确写出:
- 哪个事务被回滚(
ROLLING BACK) - 每个事务持有哪些锁(
HELD LOCKS) - 每个事务正在等待哪把锁(
WAITING FOR THIS LOCK TO BE GRANTED) - 涉及的具体表、索引、主键值
注意:该命令结果不持久,重启或多次死锁后会被覆盖;线上建议配合监控脚本自动采集,或开启 innodb_print_all_deadlocks = ON 将所有死锁写入错误日志。
真正难的不是发现死锁,而是复现和归因 —— 很多死锁只在特定数据分布+并发节奏下出现。建议在测试环境用 pt-stalk 或自建压测脚本,模拟相同 ID 序列和事务节奏,比对锁序差异。










