INNODB_TRX是排查长事务和锁等待的第一入口,需重点关注trx_started超60秒、trx_state='LOCK WAIT'及trx_query为空的事务;performance_schema.data_lock_waits可精准定位锁等待链;务必开启innodb_print_all_deadlocks以捕获完整死锁日志。

直接看 INNODB_TRX 找出卡住的长事务
长事务是死锁的温床,也是锁等待的源头。MySQL 8.4 中 INNODB_TRX 仍是第一手排查入口,但要注意它只反映“当前活跃事务”,不包含已提交或回滚的。
执行:
SELECT trx_id, trx_mysql_thread_id, trx_started, trx_state, trx_isolation_level, trx_query FROM INFORMATION_SCHEMA.INNODB_TRX ORDER BY trx_started;
-
trx_started超过 60 秒的事务必须人工介入——它大概率正在阻塞别人 -
trx_state = 'LOCK WAIT'表示该事务已被卡住,不是它在等,就是它在被等 -
trx_query为空?说明事务里只有 BEGIN 没有后续 SQL,或者执行了非 DML 语句(如 SELECT),但持有锁(比如加了FOR UPDATE) - 注意
trx_mysql_thread_id,它和SHOW PROCESSLIST中的Id一致,可用于后续 kill
用 performance_schema.data_lock_waits 看清谁在等谁
MySQL 8.0+ 已废弃 INNODB_LOCKS 和 INNODB_LOCK_WAITS,performance_schema.data_lock_waits 是唯一实时、准确的锁等待视图。它不依赖事务是否活跃,只要锁关系存在就能捕获。
执行:
SELECT r.OBJECT_SCHEMA, r.OBJECT_NAME, r.INDEX_NAME, r.ENGINE_LOCK_ID AS waiting_lock_id, b.ENGINE_LOCK_ID AS blocking_lock_id, r.THREAD_ID AS waiting_thread, b.THREAD_ID AS blocking_thread FROM performance_schema.data_lock_waits w JOIN performance_schema.data_locks r ON w.REQUESTING_ENGINE_LOCK_ID = r.ENGINE_LOCK_ID JOIN performance_schema.data_locks b ON w.BLOCKING_ENGINE_LOCK_ID = b.ENGINE_LOCK_ID;
- 结果中每行代表一个“等待链”,
waiting_thread在等blocking_thread - 结合
performance_schema.threads查线程对应的应用连接名:SELECT THREAD_ID, PROCESSLIST_USER, PROCESSLIST_HOST, PROCESSLIST_INFO FROM performance_schema.threads WHERE THREAD_ID IN (xxx, yyy); - 如果
INDEX_NAME是NULL,说明锁发生在聚簇索引(主键)上;如果是GEN_CLUST_INDEX,说明走了隐式主键,可能没建主键
别只信 SHOW ENGINE INNODB STATUS 的最后一次死锁
SHOW ENGINE INNODB STATUS 输出里的 LATEST DETECTED DEADLOCK 部分确实关键,但它只保留最近一次死锁快照。生产环境高频死锁时,这个信息早被覆盖了。
- 必须提前开启
innodb_print_all_deadlocks = ON,否则错误日志里只会写“Deadlock found when trying to get lock”而无上下文 - 检查是否生效:
SHOW VARIABLES LIKE 'innodb_print_all_deadlocks';—— 返回ON才算落地 - 死锁日志默认写入 MySQL 错误日志(路径查
SHOW VARIABLES LIKE 'log_error';),不是慢日志,也不是 general_log - 日志里出现
*** (1) TRANSACTION:和*** (2) TRANSACTION:两段,重点比对它们的 SQL、索引、锁模式(X locks rec but not gapvsS locks gap before rec)
查 data_locks 判断锁范围是否异常扩大
很多死锁不是因为并发高,而是锁住了不该锁的行。比如缺失索引导致全表扫描,或范围查询触发大量间隙锁(Gap Lock)。
执行:
SELECT ENGINE_LOCK_ID, OBJECT_SCHEMA, OBJECT_NAME, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_DATA FROM performance_schema.data_locks WHERE OBJECT_SCHEMA = 'your_db' AND OBJECT_NAME = 'your_table';
-
LOCK_TYPE = 'RECORD'是行锁,正常;LOCK_TYPE = 'TABLE'出现就危险,说明发生了锁升级或隐式锁表 -
LOCK_MODE含GAP或NEXT-KEY,说明有范围条件(如WHERE id > 100),这是间隙锁高发区 -
LOCK_DATA显示具体锁定的主键值(如105)或间隙边界(如supremum pseudo-record),可反推 SQL 是否真的只锁了预期数据 - 如果同一事务里
LOCK_DATA出现大量离散值(如1,5,12,99),说明走了索引但没走对,可能用了 OR、函数索引失效或隐式类型转换
真正难缠的不是死锁本身,而是那个锁了 5 分钟还不提交的事务——它让所有后续操作都变成“潜在死锁候选人”。别只盯着报错那一刻,先清掉长事务,再谈优化顺序和索引。


















