performance_schema.data_lock_waits 是 MySQL 8.0+ 唯一能直接映射“等待者→持有者”关系的视图,需先启用 %wait/lock% instrumentation 并确认 consumers 开启,再关联 data_locks 和 INNODB_TRX 才能闭环定位阻塞源头。

怎么用 performance_schema.data_lock_waits 直接看到“谁在等谁”
MySQL 8.0 中唯一能直接输出锁等待关系图(即有向边:A → B 表示 A 等待 B 持有的锁)的表就是 performance_schema.data_lock_waits。它不依赖事务是否活跃,只要等待存在就能捕获,比旧版 INNODB_LOCK_WAITS 更稳定、字段更语义化。
但必须先确认 instrumentation 已启用:
UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME LIKE '%wait/lock%';- 检查 consumers 是否开启:
SELECT * FROM performance_schema.setup_consumers WHERE NAME LIKE '%lock%';,确保events_statements_current和data_locks对应行是ENABLED = 'YES' - 如果查
data_lock_waits返回空,八成是 instrument 没开,不是没锁
启用后执行:
SELECT r.OBJECT_SCHEMA, r.OBJECT_NAME, r.LOCK_MODE AS requested_mode, b.LOCK_MODE AS blocking_mode, r.OWNER_THREAD_ID AS waiting_thread, b.OWNER_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 WHERE r.OBJECT_SCHEMA = 'your_db';
结果里每行就是一个等待边:waiting_thread 正卡在 blocking_thread 持有的锁上,配合 OBJECT_NAME 能立刻定位到具体表和锁粒度。
为什么只查 data_lock_waits 不够?必须连查 INNODB_TRX 和 data_locks
单独看 data_lock_waits 只知道“谁等谁”,但不知道“等什么 SQL”“持有什么锁”“锁在哪几行”。闭环定位必须三表联动:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 用
waiting_thread和blocking_thread去查performance_schema.threads,拿到PROCESSLIST_INFO(即实际执行的 SQL)和连接来源(避免误杀监控探针) - 用
blocking_trx_id查information_schema.INNODB_TRX,重点看trx_state = 'RUNNING'且trx_query IS NULL的长事务——这类“睡着了却没提交”的事务才是真凶 - 用
blocking_engine_lock_id关联data_locks,看LOCK_DATA字段:如果是123就是精确行锁;如果是min_value或跨度极大(如1, 5000),大概率是间隙锁失控或索引失效导致锁扩大
漏掉任意一环,就只能看到图的骨架,看不到血肉——比如你发现 thread 1001 在等 thread 1002,但不知道 thread 1002 其实 15 分钟前就执行完 SELECT ... FOR UPDATE 后挂起了,也没法判断它锁的是 1 行还是 2000 行。
怎么把锁等待链转成可读的调用图(例如 A → B → C)
真实生产中常出现多层嵌套等待(A 等 B,B 等 C,C 等 D),data_lock_waits 默认只存直接等待对,需要用 CTE 递归展开:
WITH RECURSIVE wait_chain AS (
SELECT
blocking_trx_id,
requesting_trx_id,
1 AS level,
CAST(requesting_trx_id AS CHAR(64)) AS path
FROM performance_schema.data_lock_waits
WHERE blocking_trx_id IN (
SELECT requesting_trx_id FROM performance_schema.data_lock_waits
)
UNION ALL
SELECT
w.blocking_trx_id,
w.requesting_trx_id,
wc.level + 1,
CONCAT(wc.path, ' → ', w.requesting_trx_id)
FROM performance_schema.data_lock_waits w
INNER JOIN wait_chain wc ON w.blocking_trx_id = wc.requesting_trx_id
)
SELECT * FROM wait_chain ORDER BY level;这个查询会把链式阻塞拉成扁平路径,比如 trx_300 → trx_200 → trx_100,一眼看出最上游是谁。但注意:MySQL 8.0 的 CTE 递归深度默认是 1000,超深链需调大 cte_max_recursion_depth;另外,该查询只适用于当前快照,锁释放后链就断了——所以得在卡顿发生时立刻跑。
容易被忽略的关键细节:锁等待图不是实时刷新的
data_lock_waits 是瞬时快照,不是流式监控。它只记录“此刻正在发生的等待”,一旦任一环节释放锁(哪怕只释放 1ms),整条链就从视图中消失。这意味着:
- 不能靠定时轮询来抓问题——等你查的时候可能已经空了
- 日志里没有对应记录,除非你同时开了
innodb_print_all_deadlocks = ON并盯住 error log - 如果
SHOW ENGINE INNODB STATUS\G里LATEST DETECTED DEADLOCK区块反复出现,说明应用逻辑里存在稳定的加锁顺序冲突,此时锁等待图只是表象,根因在代码里两个事务对同一组行用了不同顺序的UPDATE
真正要盯住的,不是图本身,而是图里反复出现的那几个 OBJECT_NAME 和 LOCK_MODE 组合——它们指向的是缺失的索引、错用的 WHERE 条件,或者没加 FOR UPDATE 却又想更新的业务逻辑。

















