mysql死锁是事务并发时资源竞争的必然结果,innodb检测到环形等待即回滚一事务并报错;典型成因是两个事务以相反顺序访问相同行,如转账中t1先锁a再锁b、t2先锁b再锁a;缺失索引会扩大锁范围至多行或全表,加剧死锁概率;可通过show engine innodb status快速定位,重点分析latest detected deadlock区块中的持锁与等待锁信息;innodb_deadlock_detect默认开启,不应关闭,应合理设置innodb_lock_wait_timeout为10~30秒。

MySQL 死锁不是配置错误,而是事务并发执行时资源竞争的必然结果;InnoDB 检测到环形等待后会立即回滚一个事务,并抛出 Deadlock found when trying to get lock 错误。
死锁发生的典型 SQL 执行顺序
两个事务以相反顺序访问相同行是最高频成因。比如转账场景中:
- 事务 T1 先
SELECT ... WHERE user_id = 'A' FOR UPDATE,再锁'B' - 事务 T2 先锁
'B',再锁'A'
只要这两个 FOR UPDATE 语句在并发下交错执行,InnoDB 就会在加锁过程中检测到等待环(T1→T2→T1),触发死锁判定。注意:不需要事务真正执行到 UPDATE 阶段,SELECT ... FOR UPDATE 本身就会加 X 锁并可能引发死锁。
为什么没索引会让死锁更易发生
缺失索引会导致锁范围从“单行”扩大为“多行甚至全表”,大幅增加交叉概率。例如:
- 表
orders上status字段无索引 - 事务 A 执行:
UPDATE orders SET paid = 1 WHERE status = 'unpaid' - 事务 B 同时执行相同语句
由于无法走索引,InnoDB 必须扫描全表,对每条匹配/不匹配的记录都加 X 锁或 GAP 锁。此时两个事务实际锁住的行集合高度重叠,且加锁顺序依赖扫描物理顺序(如主键递增),极易形成错位等待。这种“锁膨胀”在 REPEATABLE READ 隔离级别下尤为危险。
如何快速定位死锁根源(不用翻日志)
执行 SHOW ENGINE INNODB STATUS\G,重点看 LATEST DETECTED DEADLOCK 区块:
- 找
*** (1) TRANSACTION:和*** (2) TRANSACTION:对应的mysql tables in use和LOCK WAIT行 - 比对两者的
HELD LOCKS—— 看各自持有哪些记录锁(record lock)或间隙锁(gap lock) - 检查
WAITING FOR THIS LOCK TO BE GRANTED:下的 SQL,确认它试图获取哪个索引上的什么锁类型(X还是S)
常见陷阱:很多人只看 SQL 文本,却忽略 index 字段显示的其实是哪条索引被用于定位——如果显示 PRIMARY 却业务逻辑本该走 idx_user_id,说明索引失效,这才是根因。
innodb_deadlock_detect 是开还是关
默认是 ON,不要关。虽然它有轻微 CPU 开销(检测图环算法),但关掉后死锁不会自动解除,而是变成无限等待,直到触发 innodb_lock_wait_timeout(默认 50 秒)才报 Lock wait timeout exceeded。这会让问题从“秒级可恢复”退化成“分钟级阻塞”,对线上服务更致命。真正要调的是 innodb_lock_wait_timeout 值——设太短可能误杀正常长事务,设太长又拖慢故障发现,建议根据业务 RT 分位数设为 10~30 秒。











