必须秒级执行show engine innodb status\g查看latest detected deadlock区块,因mysql仅保留最近一次死锁日志,新死锁发生即覆盖旧记录,错过即不可恢复。

死锁检测超时不是超时本身的问题,而是你没在超时前拿到死锁证据——InnoDB 检测到死锁会立刻回滚,但日志只保留最后一次,不查就丢。
SHOW ENGINE INNODB STATUS 为什么经常看不到死锁?
这个命令输出的是「最后一次死锁」快照,不是实时流。如果两次死锁间隔超过几秒,中间发生的死锁就被覆盖了。线上高峰每分钟可能触发多次死锁,但你只看到最后一条,前面的全丢了。
- 执行
SHOW ENGINE INNODB STATUS\G后必须立刻复制输出,不能等、不能翻页 - 输出里关键字段是
LATEST DETECTED DEADLOCK,下面紧跟着两个事务的 SQL、锁类型(X表示排他锁)、索引名和行号 - 如果没这段,说明不是死锁,而是普通锁等待超时(
ERROR 1205或ERROR 1213要分清)
怎么捕获「正在发生」的锁等待链?
靠 INNODB_TRX + INNODB_LOCK_WAITS 关联查,比等死锁日志更及时,能定位谁卡着谁。
- 先查等锁的事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TRX_STATE = 'LOCK WAIT' - 再用返回的
TRX_WAITING_TRX_ID去INNODB_LOCK_WAITS查BLOCKING_TRX_ID - 最后用
BLOCKING_TRX_ID查INNODB_TRX,看那个事务的TRX_STARTED时间——如果早于 30 秒,大概率是长事务没提交 - 注意:
TRX_QUERY可能为 NULL,说明是隐式锁(比如 INSERT 唯一键冲突后回滚残留),这时得结合PROCESSLIST看State是不是updating却没 SQL
WHERE 条件没走索引会导致 UPDATE 锁扩大
UPDATE 不走索引 → 全表扫描 → 每行加记录锁 + 间隙锁 → 锁范围爆炸,和其他事务交叉概率飙升。这不是慢,是「锁得太多太宽」。
- 用
EXPLAIN SELECT *模拟条件(MySQL 不支持EXPLAIN UPDATE):重点看key是否为NULL,rows_examined是否远大于你要改的行数 - 避免对字段用函数:
WHERE DATE(created_at) = '2024-01-01'改成WHERE created_at >= '2024-01-01' AND created_at - 联合索引失效常见:有
(status, updated_at)索引,但写WHERE updated_at > 'xxx'就完全用不上
死锁日志里锁类型 X/S/GAP 怎么快速看懂?
日志里一行锁信息类似:RECORD LOCKS space id 123 page no 456 n bits 72 index `PRIMARY` of table `db`.`t` trx id 12345 lock_mode X locks rec but not gap ——关键就三块。
-
lock_mode X:排他锁(UPDATE/DELETE 默认),S是共享锁(SELECT ... LOCK IN SHARE MODE) -
locks rec but not gap:只锁记录,不锁间隙;locks gap before rec是纯间隙锁;locks next key是记录+间隙(Next-Key Lock),RR 隔离级别默认行为 -
index `PRIMARY`:锁在哪条索引上。如果看到锁在二级索引,但 UPDATE 条件没走该索引,说明优化器选错了执行计划,得强制指定索引或重建索引
真正难的不是查到死锁,而是从日志里还原出两个事务的加锁顺序——那才是循环依赖的根源。很多团队查完日志就加重试,结果重试把锁竞争放大了三倍。锁顺序不对,重试只是让死锁来得更快。










