mysql 8.0需在事务未提交时立即查performance_schema.data_locks,通过thread_id关联threads和events_statements_current,结合object_name、index_name、lock_data和lock_mode精准定位sql所加的表/行/间隙锁。

怎么用 performance_schema.data_locks 查每条 SQL 加了哪些锁
MySQL 8.0 后,data_locks 是唯一能精确到「哪条 SQL、锁了哪个索引、哪一行(或间隙)」的视图。它不依赖事务状态快照,而是实时捕获引擎层加锁动作。但默认关闭锁采集,且字段含义容易误读。
实操前先确认开关已开:SELECT COUNT(*) FROM performance_schema.setup_instruments WHERE NAME = 'transaction' AND ENABLED = 'YES'; 返回 1 才有效;若为 0,执行 UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME = 'transaction';
-
THREAD_ID对应performance_schema.threads中的线程,可联查PROCESSLIST_ID定位是哪个连接 -
OBJECT_NAME和INDEX_NAME告诉你锁在哪个表、哪个索引上——注意NULL表示聚簇索引(即主键),GEN_CLUST_INDEX是老版本遗留名,实际就是主键 -
LOCK_DATA是关键:对主键是具体值(如123),对二级索引可能是'Eason', 123(索引列值 + 主键值),对 GAP 锁则显示范围(如5, 10表示 (5,10) 间隙) -
LOCK_MODE中的X locks rec but not gap是行锁,X locks gap before rec是间隙锁,INSERT_INTENTION是插入意向锁——三者混用才容易死锁
为什么 SELECT * FROM performance_schema.data_locks 看不到刚执行的 UPDATE 的锁
不是 SQL 没加锁,而是锁被释放得太快,或者你查的时机不对。data_locks 只记录「当前持有中」的锁,事务一提交、回滚或语句执行完(非事务级锁),锁就没了。
常见错误场景:
- 在应用里执行
BEGIN; UPDATE t SET x=1 WHERE id=1; COMMIT;后再查data_locks—— 锁早已释放,必然为空 - 没加
FOR UPDATE或LOCK IN SHARE MODE的普通SELECT在 RR 隔离级别下不加锁(除非触发一致性读快照重建) - 查询时事务还没真正走到加锁那步,比如 SQL 正在解析 WHERE 条件、做隐式转换,锁还没申请
正确做法:在事务未提交时立刻查,例如会话 A 执行 BEGIN; UPDATE t SET name='x' WHERE id=5; 后**不提交**,会话 B 立即执行 SELECT * FROM performance_schema.data_locks WHERE THREAD_ID = ?;(? 为会话 A 的 THREAD_ID)
如何把锁和具体 SQL 语句关联起来
data_locks 本身不含 SQL 文本,必须通过 THREAD_ID 关联到正在执行的语句。MySQL 8.0 推荐路径是:用 data_locks 找出持锁线程 → 查 performance_schema.threads 得 PROCESSLIST_ID → 再查 performance_schema.events_statements_current 获取该线程最近执行的 SQL。
一条命令串起来(需有 PROCESS 权限):
SELECT t.PROCESSLIST_ID AS pid, t.PROCESSLIST_INFO AS current_sql, l.OBJECT_SCHEMA, l.OBJECT_NAME, l.INDEX_NAME, l.LOCK_TYPE, l.LOCK_MODE, l.LOCK_DATA FROM performance_schema.data_locks l JOIN performance_schema.threads t ON l.THREAD_ID = t.THREAD_ID JOIN performance_schema.events_statements_current e ON t.THREAD_ID = e.THREAD_ID WHERE t.PROCESSLIST_ID IS NOT NULL;
注意:events_statements_current 默认只存当前语句,如果事务里跑了多条 SQL,得提前开启 events_statements_history_long 并查对应历史表。
为什么 LOCK_DATA 有时显示 NULL 或一堆十六进制
LOCK_DATA 为 NULL 不代表没锁,而是锁对象不可序列化输出,常见于:
- 表锁(
LOCK_TYPE = 'TABLE')或元数据锁(MDL),此时锁粒度在表/库级,无具体行值 - 对
TEXT/BLOB字段加锁时,InnoDB 可能不记录完整值,只标NULL - 使用压缩页或加密表空间时,
LOCK_DATA可能以十六进制形式呈现(如0x78617364),需用HEX()或CONVERT()辅助识别
真正要警惕的是:当 LOCK_DATA 大量重复(如全是 1)、跨度极大(如从 1 到 99999),大概率是索引失效导致全表扫描加锁——这时得立刻回查 EXPLAIN 和 data_locks 的 LOCK_TYPE 组合。
最易被忽略的一点:data_locks 记录的是 InnoDB 层的锁,和 Server 层的 MDL(表级锁)完全无关。看到业务卡住却查不到行锁,先跑一遍 sys.schema_table_lock_waits——别默认所有“锁”都是行锁。











