应先查innodb_trx筛选trx_state='lock wait'或'running'并按trx_started升序排序,重点识别等待锁的事务和长期sleep未提交的危险事务;再用show engine innodb status\g定位latest detected deadlock段分析锁冲突细节。

查 INNODB_TRX 先揪出“卡住”和“装睡”的事务
死锁发生后,第一反应不是翻日志,而是立刻连上数据库执行:SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_state IN ('LOCK WAIT', 'RUNNING') ORDER BY trx_started ASC;。重点看两件事:
-
trx_state = 'LOCK WAIT'的事务——它们已明确在等锁,但未必是死锁参与者(可能只是被波及) -
trx_state = 'RUNNING'且trx_command = 'Sleep'的事务——最危险。它没在执行 SQL,却长期未提交,持有的锁一直不放,常是死锁链的起点 - 注意
trx_started时间戳:越早开始的事务,越可能是长期未COMMIT或ROLLBACK的“幽灵连接”
用 SHOW ENGINE INNODB STATUS\G 定位真实锁冲突点
INNODB_LOCK_WAITS 在 MySQL 5.7 中不可靠,字段缺失、blocking_trx_id 经常为空,不能直接信。真正可信的是 SHOW ENGINE INNODB STATUS\G 输出里的 LATEST DETECTED DEADLOCK 段落:
- 它会明确标出两个(或多个)事务的
trx_id、各自WAITING FOR THIS LOCK TO BE GRANTED和HOLDS THE LOCK(S) - 关键要看锁的类型:
lock_mode X locks gap before rec表示间隙锁冲突;lock_mode X locks rec but not gap是纯记录锁;insert intention waiting往往指向唯一索引冲突或并发INSERT ... ON DUPLICATE KEY UPDATE - 锁位置由
space id、page no、index name组成,可反查对应表和索引:SELECT TABLE_NAME, INDEX_NAME FROM INFORMATION_SCHEMA.INNODB_SYS_INDEXES WHERE SPACE = ? AND PAGE_NO = ?
对照 INNODB_LOCKS 看锁粒度与索引选择
INNODB_LOCKS 能告诉你每把锁具体锁了什么,但需手动关联:
- 先从
SHOW ENGINE INNODB STATUS中拿到等待事务的waiting_trx_id,再查SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS WHERE lock_trx_id = 'xxx'; - 重点关注
lock_type(RECORD/GAP/NEXT-KEY)、lock_mode(S/X)、lock_index(哪个索引被锁) - 常见陷阱:
UPDATE语句若没走主键或唯一索引,可能触发全表扫描+间隙锁;INSERT ... ON DUPLICATE KEY UPDATE在 RR 隔离下会先加 S 锁再升级 X 锁,极易与其它事务形成循环 - 如果
lock_index是非唯一二级索引,且lock_mode含gap,基本可以断定是间隙锁死锁——这时优化方向不是加索引,而是改查询条件或降隔离级别
别漏掉事务上下文:隔离级别与自动提交状态
同一句 SQL 在不同事务上下文里,加锁行为天差地别:
- 查当前会话隔离级别:
SELECT @@transaction_isolation;。MySQL 5.7 默认REPEATABLE-READ,间隙锁默认开启;READ-COMMITTED下间隙锁仅用于唯一键检查,但仍有风险 - 查自动提交:
SELECT @@autocommit;。值为0表示手动事务,任何UPDATE/INSERT后没COMMIT就等于挂起一把锁 - 业务代码里常见坑:Spring
@Transactional方法抛异常但没配置rollbackFor,导致事务没回滚;连接池归还连接前未重置事务状态
真正难的不是看到锁,而是判断“为什么这把锁会被持住这么久”。INNODB_TRX 里的 trx_query 常为空(比如 Sleep 状态),此时必须结合应用日志、线程堆栈、连接来源 IP,才能确认那个“装睡”的事务到底卡在哪一行代码里。











