mysql 8.0+ 中,performance_schema.data_lock_waits 是唯一能直接映射“等待者→持有者”关系的视图,需先启用 wait/lock 相关 instrument 才可查到锁等待链,并须关联 data_locks 和 innodb_trx 才能闭环定位阻塞源头。

查锁等待链:用 performance_schema.data_lock_waits 看清谁在等谁
MySQL 8.0+ 中,data_lock_waits 是唯一能直接映射“等待者→持有者”关系的视图,比旧版 INNODB_LOCK_WAITS 更准确、字段更清晰。它不依赖事务是否活跃,只要锁等待存在,就能捕获。
- 必须提前启用相关 instrument:
UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME LIKE '%wait/lock%'; - 执行后立刻查:
SELECT * FROM performance_schema.data_lock_waits;,重点关注BLOCKING_ENGINE_TRANSACTION_ID和REQUESTING_ENGINE_TRANSACTION_ID - 别只看这一张表——要连查
performance_schema.data_locks(找锁对象)和information_schema.INNODB_TRX(查事务线程ID、SQL、运行时长)才能闭环 - 常见误操作:没开 instrument 就查,结果返回空;或只查了
data_lock_waits却不关联事务信息,看不出是哪个应用连接、哪条 SQL 在卡住
定位阻塞源头:从 INNODB_TRX 找出“空闲但未提交”的事务
大量锁等待不是因为并发高,而是某个事务早该结束了却一直挂着——比如应用异常断开但没 rollback,或代码里漏了 COMMIT/ROLLBACK。这类事务在 INNODB_TRX 里表现为 trx_state = 'RUNNING' 但 trx_started 时间极早、trx_query 为空、trx_operation_state 是 'sleeping before entering InnoDB'。
- 快速筛查语句:
SELECT trx_id, trx_mysql_thread_id, trx_started, trx_state, trx_query FROM information_schema.INNODB_TRX WHERE trx_state = 'RUNNING' AND (trx_query IS NULL OR trx_query = '') ORDER BY trx_started ASC LIMIT 10; - 注意隔离级别影响:RR 级别下,即使只是
SELECT ... FOR UPDATE后没提交,也会持续持有锁;RC 级别下部分锁会释放得快些,但 gap lock 仍可能残留(尤其涉及唯一键冲突检查时) - 别直接 kill 线程——先用
SELECT * FROM performance_schema.threads WHERE THREAD_ID = ?查对应连接的PROCESSLIST_INFO,确认是不是监控探针或定时任务的“假活跃”连接
分析死锁现场:SHOW ENGINE INNODB STATUS\G 的关键字段怎么看
这是最轻量、最实时的死锁快照工具,不需要任何配置,但输出信息杂乱,真正有用的只有 TRANSACTIONS 段里的 LATEST DETECTED DEADLOCK 块。重点不是读全文,而是抓三组对应关系:
-
TRANSACTION [id]→ 对应后面HOLDS THE LOCK(S)和WAITING FOR THIS LOCK的归属 -
space id+page no+index→ 定位到具体索引页,结合SELECT * FROM information_schema.INNODB_SYS_INDEXES WHERE INDEX_ID = ?反查是哪张表哪个索引 - 末尾的两段 SQL(通常是
UPDATE/DELETE)→ 直接暴露业务逻辑冲突点,比如一个按user_id更新,另一个按order_no删除,但两者都命中同一行 - 容易忽略的坑:
SHOW ENGINE INNODB STATUS只保留最近一次死锁,如果并发高频死锁,这个信息会被覆盖;且它不显示被回滚事务的完整堆栈,需配合应用日志交叉验证
验证锁行为:用最小化事务复现,避开 ORM 隐式干扰
很多死锁在生产环境偶发、测试环境不复现,根本原因是 ORM(如 MyBatis、GORM)自动生成 SQL 的 where 条件顺序不一致,或隐式开启事务导致锁范围不可控。诊断时务必绕过框架,用原生客户端直连复现。
- 步骤:建两张简单表 → 开两个 mysql 客户端窗口 → 分别
BEGIN→ 按疑似冲突顺序执行SELECT ... FOR UPDATE或UPDATE→ 观察第二个是否卡住 - 关键控制点:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;(临时切 RC)对比 RR 行为,确认是否间隙锁引发;加EXPLAIN FORMAT=tree看是否走了预期索引,避免全表扫描放大锁范围 - 真实陷阱:有些“复现失败”是因为测试数据分布不同——比如死锁依赖特定 page 分布,而测试库数据量小,所有记录挤在一个 page 里,锁竞争模式完全不同
复杂死锁往往不是单点问题,而是锁粒度、索引设计、事务边界、应用调用顺序四者叠加的结果。最常被跳过的一步,是确认那条“被回滚的 SQL”在业务上是否真的可以重试——有时候它改的是状态机终态字段,重复执行会破坏业务一致性,这时候光优化 SQL 没用,得改流程。











