MySQL死锁是多个事务以不同顺序加锁同一组行导致的循环等待,InnoDB自动检测并回滚代价小的事务,报错“Deadlock found when trying to get lock”;根本原因包括UPDATE未走索引致全表加锁、间隙锁冲突、批量IN列表加锁顺序不可控、SELECT FOR UPDATE在无记录时加间隙锁等;排查用SHOW ENGINE INNODB STATUS查看LATEST DETECTED DEADLOCK,预防需统一加锁顺序、确保索引命中、避免隐式转换、禁用非确定性条件与LIMIT混用、优先用ON DUPLICATE KEY UPDATE替代。

MySQL高并发 UPDATE 出现死锁,不是并发量大本身导致的,而是多个事务以不同顺序加锁同一组行时必然触发的循环等待——InnoDB 检测到后会回滚其中一个事务并报 Deadlock found when trying to get lock,但业务若没做幂等重试或锁行为不可控,就会反复失败。
UPDATE WHERE 没走索引,锁范围直接失控
WHERE 条件无索引时,InnoDB 退化为全表扫描,每行都加记录锁(X),还可能因 Using filesort 导致加锁顺序完全不可控。两个并发 UPDATE 扫同一张表,极易形成交叉等待。
- 用
EXPLAIN FORMAT=TRADITIONAL验证:重点看key是否命中预期索引、Extra是否含Using filesort或Using where - 复合索引注意最左前缀:有
(status, id)索引,WHERE status = 'pending' ORDER BY id有效;但WHERE id > 100基本失效 - 避免在 WHERE 中用函数或隐式转换:
WHERE DATE(create_time) = '2026-09-01'会跳过索引
批量 UPDATE 的 IN 列表和 LIMIT 都不保序
UPDATE ... WHERE id IN (2,1) 和 UPDATE ... WHERE id IN (1,2) 在 InnoDB 中加锁顺序不取决于 SQL 字面顺序,而取决于索引 B+ 树遍历路径。这会让加锁顺序在不同事务间天然不一致。
- 改用游标式更新:
UPDATE t SET status='processing' WHERE id > ? AND status='pending' ORDER BY id LIMIT 100,每次记录上一批最大id值作为下一批起点 - 禁用
LIMIT做分页控制:它不保证幂等性,数据变动时可能漏行或重复处理 - 避免
WHERE status IN ('a','b')这类非确定性条件与LIMIT混用,不同事务扫描到的实际行集可能不同
SELECT FOR UPDATE 查不到记录也死锁
在 RR 隔离级别下,SELECT * FROM orders WHERE order_no = 'ABC' FOR UPDATE 即使没查到任何记录,也会对 order_no 所在索引间隙加 Gap Lock。多个并发请求争抢同一间隙,再叠加后续 INSERT 的插入意向锁,立刻构成死锁链。
- 优先用
INSERT INTO ... ON DUPLICATE KEY UPDATE替代,前提是order_no有UNIQUE约束 - 若必须用
SELECT FOR UPDATE,确保 WHERE 字段有唯一索引,且EXPLAIN显示type = const或ref、rows = 1 - 绝对避免对高重复值字段(如
name)做SELECT FOR UPDATE,间隙锁冲突概率陡增
怎么快速定位死锁源头
死锁日志里真正关键的不是“谁被回滚了”,而是“两个事务各自持有什么锁、又在等什么锁”——这才是加锁行为是否可收敛的证据。
- 执行
SHOW ENGINE INNODB STATUS\G,聚焦LATEST DETECTED DEADLOCK段,看*** (1) TRANSACTION和*** (2) TRANSACTION的 SQL、持有的锁、等待的锁 - 开启持久化死锁日志:
innodb_print_all_deadlocks = 1写入错误日志,方便复盘高频模式 - 检查
information_schema.INNODB_TRX和INNODB_LOCK_WAITS,确认是否存在长事务拖慢整体锁释放节奏
真正难处理的不是单次死锁,而是加锁行为和业务逻辑耦合后形成的隐式依赖——比如一个 UPDATE 依赖另一个事务刚 INSERT 的记录,而那个 INSERT 还没提交。这时候即使索引全命中,也可能因锁等待超时暴露设计缺陷。


















