必须手动启用wait/lock类instruments和events_waits相关consumers,验证需见至少3项锁采集器为YES;data_lock_waits仅存瞬时等待关系,须关联INNODB_TRX、data_locks及threads才能准确定位阻塞源头与锁粒度。

怎么确认performance_schema锁监控已真正生效
很多人查data_lock_waits返回空,第一反应是“没死锁”,其实大概率是锁采集器根本没开。MySQL 默认启用 performance_schema,但wait/lock类仪器(instruments)和消费者(consumers)默认是关闭的。
必须手动开启这两类配置:
UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES' WHERE NAME LIKE 'wait/lock%';UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME IN ('events_waits_current', 'events_waits_history_long');
执行后无需重启——MySQL 8.0.16+ 支持动态生效。验证是否成功:SELECT * FROM performance_schema.setup_instruments WHERE NAME LIKE 'wait/lock%' AND ENABLED = 'YES'; 至少要有 wait/lock/table/sql/handler、wait/lock/metadata/sql/mdl、wait/lock/innoDB/lock 几项为 YES。
怎么用data_lock_waits快速抓到等待链
performance_schema.data_lock_waits 是 MySQL 8.0+ 中唯一能直接表达「谁在等谁」的视图,但它只存关系,不存上下文。单独查它容易误判。
正确用法是三表关联,一次性定位到 SQL 和线程:
- 先查等待关系:
SELECT REQUESTING_ENGINE_TRANSACTION_ID, BLOCKING_ENGINE_TRANSACTION_ID FROM performance_schema.data_lock_waits; - 再连查事务详情:
SELECT r.trx_id AS waiting_trx, r.trx_query AS waiting_sql, b.trx_id AS blocking_trx, b.trx_query AS blocking_sql FROM performance_schema.data_lock_waits w JOIN information_schema.INNODB_TRX r ON w.REQUESTING_ENGINE_TRANSACTION_ID = r.trx_id JOIN information_schema.INNODB_TRX b ON w.BLOCKING_ENGINE_TRANSACTION_ID = b.trx_id;
注意:INNODB_TRX.trx_query 可能为空(比如事务刚启动还没执行 SQL,或已提交但未清理),此时要结合 trx_started 和 trx_state 判断是否是“挂着不提交”的幽灵事务。
为什么光看锁等待还不够:必须对齐data_locks和索引行为
知道 A 在等 B 不代表你知道 A 为什么卡在那条 SQL 上。关键在 performance_schema.data_locks —— 它告诉你每把锁具体锁了什么。
典型排查路径:
- 从
data_lock_waits拿到REQUESTING_ENGINE_TRANSACTION_ID - 去
data_locks过滤:SELECT * FROM performance_schema.data_locks WHERE ENGINE_TRANSACTION_ID = ? AND LOCK_DATA IS NOT NULL; - 重点看:
LOCK_TYPE(RECORD / GAP / TABLE)、LOCK_MODE(X / S)、LOCK_INDEX(PRIMARY 还是二级索引?)、LOCK_DATA(具体记录值,如123或'user_abc')
如果发现同一事务持有大量 LOCK_TYPE = 'GAP' 且 LOCK_DATA 跨度极大(比如从 100 到 9999),基本可断定是 WHERE 条件没走索引,触发了全范围 next-key 锁 —— 这才是死锁高频复现的根因,不是并发高,是锁粒度失控。
怎么避免把阻塞源误杀成无辜线程
看到 blocking_trx_id 就想 KILL?危险。很多“阻塞者”其实是 sleep 状态的旧连接,INNODB_TRX 显示 trx_state = 'RUNNING' 但 trx_query = NULL,trx_operation_state 是 'sleeping before entering InnoDB'。
真正该查的是它背后的应用连接:
- 用
trx_mysql_thread_id去performance_schema.threads查:SELECT PROCESSLIST_INFO FROM performance_schema.threads WHERE THREAD_ID = ?; - 如果
PROCESSLIST_INFO是空或类似NULL、Connect、Binlog Dump,大概率是连接池未正确 close 或应用异常退出留下的僵尸连接 - 这类连接不能直接 KILL,应先确认是否属于监控探针、定时任务或已下线服务,否则可能中断关键链路
最易被忽略的一点:间隙锁(GAP)本身不阻塞读,也不阻塞同间隙内的其他 GAP 锁,但它会阻塞插入意向锁(INSERT_INTENTION)。所以两个事务看似“都没改数据”,却因同时 INSERT 相邻值而死锁——这种场景下,data_locks 里看不到 RECORD 锁,只看到一堆 GAP + INSERT_INTENTION 冲突,必须盯紧 LOCK_MODE 和 LOCK_REQUEST_MODE 字段。











