频繁死锁指每分钟持续出现3+次同表相似SQL的ERROR 1213,需用performance_schema或错误日志聚合分析,而非仅看INNODB STATUS;根因多为索引失效导致锁升级,修复优先补索引。

死锁频繁发生,说明事务逻辑或索引设计存在系统性缺陷,不能只依赖 innodb_deadlock_detect=ON 自动回滚——它只是兜底机制,不是解决方案。
怎么看死锁是不是真频繁?别被日志刷屏带偏节奏
高频死锁的判断标准不是“每天报几次”,而是看单位时间内(比如每分钟)是否持续出现多个 ERROR 1213 (40001): Deadlock found when trying to get lock。如果每分钟有 3+ 次,且集中在同一张表、相似 SQL,就属于真频繁。
容易误判的情况:
- 应用层重试逻辑没做,一次失败就反复重发相同事务,日志里看起来“高频”,实际是单次死锁被放大
- 运维批量脚本(如定时归档)和业务流量叠加,导致短时尖峰,不是常态问题
-
SHOW ENGINE INNODB STATUS输出中“LATEST DETECTED DEADLOCK”只保留最后一次,之前死锁已丢失,不能靠它统计频次
正确做法:用 performance_schema.data_locks + performance_schema.data_lock_waits(MySQL 8.0+)或定期采集 information_schema.INNODB_TRX 配合错误日志时间戳做聚合分析。
为什么 innodb_deadlock_detect 开关对“频繁”没用?
这个参数默认就是 ON,InnoDB 从 5.6 起就强制启用死锁检测,关掉它只会让事务无限等待(直到 innodb_lock_wait_timeout 触发超时),反而更伤可用性。
真正影响“感知频率”的是两个参数:
-
innodb_lock_wait_timeout:设太小(如 5 秒),会让本可等待成功的事务提前超时,被误记为“死锁相关失败”;设太大(如 300 秒),又拖慢故障暴露速度 -
innodb_rollback_on_timeout:MySQL 5.7+ 默认OFF,意味着超时事务不会自动回滚,残留锁可能引发后续真实死锁——这个才是隐蔽推手
建议值:innodb_lock_wait_timeout=30,innodb_rollback_on_timeout=ON(必须显式开启)。
查 SHOW ENGINE INNODB STATUS 时重点盯哪几行?
输出里真正有用的只有三块,其他全是干扰信息:
- “LATEST DETECTED DEADLOCK” 下方的两个事务块:看
TRANSACTION行的id和mysql thread id,确认是不是你业务线的连接 - 每个事务块里的
LOCK WAIT行:明确哪个语句卡在等锁,而不是执行中 - 最后的
*** WE ROLL BACK TRANSACTION (1):数字是事务 ID,不是序号;被回滚的未必是“错”的那个,InnoDB 按代价选——通常选 INSERT/UPDATE 行数少的
关键陷阱:很多人只看 SQL 文本,却忽略 lock_mode X locks rec but not gap 这类锁类型描述。如果是 locks gap before rec,基本能断定是唯一索引缺失或范围查询惹的祸。
索引失效比加锁顺序错更致命,先验这个再调逻辑
90% 的“频繁死锁”根因是索引没走对,导致锁从行级扩大到间隙甚至临键锁。典型信号:
- 死锁日志里出现大量
lock_mode X locks gap before rec - SQL 中
WHERE条件用了函数、隐式类型转换、OR或LIKE '%xxx' - 联合索引只用左前缀,但
ORDER BY或GROUP BY用了非前缀字段
验证方法:对出问题的 SQL 执行 EXPLAIN FORMAT=TRADITIONAL,重点看 key 是否为预期索引、rows 是否远大于实际匹配行数、Extra 有没有 Using filesort 或 Using temporary。
修复优先级:补索引 > 改 SQL 写法 > 调事务顺序。比如 UPDATE t SET a=1 WHERE b=2 AND c>3,若只有 (b) 索引,加 (b,c) 联合索引就能砍掉 70% 死锁。
最常被跳过的动作:检查 FOR UPDATE 语句是否真的需要锁全表扫描结果。很多代码写成 SELECT * FROM t WHERE status=0 FOR UPDATE,却不加 status 索引——这等于默认给所有未处理记录加锁,一并发就撞车。











