必须启用performance_schema及data_locks消费者,再join data_locks与threads表按thread_id分组统计锁数并排序取top 10,where过滤null processlist_id以排除后台线程,group by需包含所有非聚合字段,结果lock_count为锁实例数而非锁对象数。

查 performance_schema 的 data_locks 和 threads 表
MySQL 8.0+ 的锁信息默认只在 performance_schema 中暴露,information_schema.INNODB_TRX 等旧表不包含锁数量统计。必须确保 performance_schema 已启用,且相关消费者已打开:
-
data_locks记录每个事务当前持有的每一把锁(行锁、表锁、意向锁等) -
threads提供线程 ID(THREAD_ID)与会话 ID(PROCESSLIST_ID)的映射 - 需要 JOIN 这两张表,并按
THREAD_ID分组计数
执行前确认开关状态:
SELECT * FROM performance_schema.setup_consumers WHERE NAME LIKE 'global_instrumentation' OR NAME LIKE 'thread%';
若 data_locks 消费者为 OFF,需手动开启:
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME = 'global_instrumentation';<br>UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME = 'thread_instrumentation';
用 COUNT(*) 统计每线程锁数并排序取 Top 10
核心逻辑是:对每个 THREAD_ID 统计其在 data_locks 中的记录数,再关联 threads 获取可读的 PROCESSLIST_ID 和 NAME(如 thread/sql/one_connection)。
典型查询语句如下:
SELECT<br> t.PROCESSLIST_ID AS `id`,<br> t.PROCESSLIST_USER AS `user`,<br> t.PROCESSLIST_HOST AS `host`,<br> t.PROCESSLIST_DB AS `db`,<br> COUNT(*) AS `lock_count`<br>FROM performance_schema.data_locks dl<br>JOIN performance_schema.threads t ON dl.THREAD_ID = t.THREAD_ID<br>WHERE t.PROCESSLIST_ID IS NOT NULL<br>GROUP BY t.THREAD_ID, t.PROCESSLIST_ID, t.PROCESSLIST_USER, t.PROCESSLIST_HOST, t.PROCESSLIST_DB<br>ORDER BY lock_count DESC<br>LIMIT 10;
注意点:
-
WHERE t.PROCESSLIST_ID IS NOT NULL排除后台线程(如thread/sql/main),只看用户连接 - 必须显式
GROUP BY所有非聚合字段,否则 MySQL 8.0+ 严格模式下报错ERROR 1055 - 结果中的
lock_count是“锁实例数”,不是“锁对象数”——同一行上的 S 锁和 X 锁算两把,意向锁也单独计数
对比 sys.innodb_lock_waits 视图是否适用?
sys.innodb_lock_waits 是只展示“正在阻塞/被阻塞”的锁等待关系,它不统计“持有但未造成等待”的锁。也就是说:
- 空闲事务长时间持有行锁(比如未提交的 UPDATE)不会出现在该视图中
- 只读事务加的
LOCK_S(一致性读不加锁,但SELECT ... LOCK IN SHARE MODE会)也不会触发等待,因此不可见 - 所以不能用它找“持锁最多”的线程,只能找“当前谁卡住了谁”
真正要定位潜在锁竞争源头,必须回到 data_locks 原始表。
权限与性能影响要提前评估
执行上述查询至少需要:
-
SELECT权限在performance_schema.* - 如果是普通用户,还需
SYSTEM_VARIABLES_ADMIN或PERFORMANCE_SCHEMA_ADMIN才能修改setup_consumers
性能方面:
-
data_locks是内存表,实时采集,查询本身开销小 - 但若系统正经历高并发锁争用(比如上万把锁),
COUNT(*) GROUP BY可能短暂增加 CPU 压力 - 生产环境建议避开业务高峰执行,或加
SQL_NO_CACHE避免干扰查询缓存(虽然 PS 表本身不走 Query Cache)
最常被忽略的是:锁数量暴涨往往源于长事务或未提交事务,查出 top 线程后,务必用 SELECT * FROM performance_schema.threads WHERE PROCESSLIST_ID = ? 确认其 PROCESSLIST_TIME 和 PROCESSLIST_INFO,看是不是卡在某个 DML 后忘了 COMMIT。











