MySQL 锁等待超时怎样从 performance_schema 找阻塞者
来源:17golang原创
时间:2026-10-20 22:57:24 458浏览 收藏
遇到 MySQL 锁等待超时,我通常不会先猜是哪条业务 SQL 慢,而是先把“等待关系”捞出来:哪个事务在等、哪个事务持有冲突锁、它们分别对应哪个连接。对 InnoDB 来说,performance_schema.data_lock_waits 给出锁依赖,information_schema.innodb_trx 补充事务语句,最后再用 performance_schema.threads 找到会话信息。
官方地址:https://dev.mysql.com/doc/refman/8.4/en/
最实用的排查路径是:data_lock_waits判断关系,data_locks看资源,innodb_trx看事务,threads找会话。查询结果只是快速变化的现场快照,发现阻塞者后应马上结合业务日志和事务边界处理。
先把等待关系看成一条连接线
data_lock_waits 不是“慢查询排行榜”,而是锁请求之间的关系表。REQUESTING_ENGINE_TRANSACTION_ID 表示正在请求锁的事务,BLOCKING_ENGINE_TRANSACTION_ID 表示当前持有冲突锁的事务。一个等待事务可能对应多行,因为它的请求可能同时受到多个已持有锁的影响。
这张静态结构图把本文会用到的表和字段放在一起:先从等待关系取两端事务,再从事务找到 SQL,最后将连接 ID 映射到会话。

第一条查询:同时找出等待者和阻塞者
先执行下面这条最小查询。它直接沿着官方文档给出的关系,把同一条等待记录的两端事务展开。字段名刻意保留 waiting_ 和 blocking_ 前缀,避免值很多时看反。
SELECT
r.trx_id AS waiting_trx_id,
r.trx_mysql_thread_id AS waiting_connection_id,
r.trx_state AS waiting_state,
r.trx_started AS waiting_started,
r.trx_wait_started AS waiting_wait_started,
r.trx_query AS waiting_query,
b.trx_id AS blocking_trx_id,
b.trx_mysql_thread_id AS blocking_connection_id,
b.trx_state AS blocking_state,
b.trx_started AS blocking_started,
b.trx_query AS blocking_query
FROM performance_schema.data_lock_waits AS w
JOIN information_schema.innodb_trx AS r
ON r.trx_id = w.requesting_engine_transaction_id
JOIN information_schema.innodb_trx AS b
ON b.trx_id = w.blocking_engine_transaction_id
ORDER BY r.trx_wait_started;
-- waiting_* 是正在等锁的事务,blocking_* 是持有冲突锁的事务
-- trx_mysql_thread_id 可继续映射到会话连接,NULL 语句不代表没有阻塞
读结果时先看三列:waiting_query 是当前卡住的请求,blocking_query 是阻塞事务最近关联的语句,blocking_started 可以帮助判断它是否长时间未结束。如果同一等待事务出现多行,不要立刻去重;这可能是它同时被多个锁资源挡住。
第二条查询:把锁资源和模式补齐
只知道事务 ID 还不够。要判断是同一行、同一索引,还是表级资源,可以把请求锁和阻塞锁分别连接到 data_locks。同一张表要连接两次:一次给等待者的请求锁,一次给阻塞者持有的锁。
SELECT
w.requesting_engine_transaction_id AS waiting_trx_id,
w.blocking_engine_transaction_id AS blocking_trx_id,
req.object_schema AS waiting_schema,
req.object_name AS waiting_table,
req.index_name AS waiting_index,
req.lock_type AS waiting_lock_type,
req.lock_mode AS waiting_lock_mode,
hold.object_schema AS blocking_schema,
hold.object_name AS blocking_table,
hold.index_name AS blocking_index,
hold.lock_type AS blocking_lock_type,
hold.lock_mode AS blocking_lock_mode,
req.lock_data AS waiting_lock_data,
hold.lock_data AS blocking_lock_data
FROM performance_schema.data_lock_waits AS w
JOIN performance_schema.data_locks AS req
ON req.engine_lock_id = w.requesting_engine_lock_id
AND req.engine = w.engine
JOIN performance_schema.data_locks AS hold
ON hold.engine_lock_id = w.blocking_engine_lock_id
AND hold.engine = w.engine;
-- req 是等待中的锁请求,hold 是挡住它的已持有锁
-- engine 条件避免不同存储引擎出现同名锁 ID 的歧义
重点不是把所有列都打印出来,而是核对 object_schema、object_name、index_name、lock_type 和 lock_mode 是否指向同一个资源。lock_data 可作为定位线索,但不要把它当成稳定的业务主键展示给用户。
第三条查询:从事务连接到会话信息
innodb_trx.trx_mysql_thread_id 是连接层 ID,而 Performance Schema 的 threads 同时保存 PROCESSLIST_ID 和内部 THREAD_ID。用前者关联,后者才能继续查询语句历史。

SELECT
r.trx_id AS waiting_trx_id,
r.trx_mysql_thread_id AS waiting_connection_id,
tw.thread_id AS waiting_thread_id,
tw.processlist_user AS waiting_user,
tw.processlist_host AS waiting_host,
tw.processlist_db AS waiting_db,
b.trx_id AS blocking_trx_id,
b.trx_mysql_thread_id AS blocking_connection_id,
tb.thread_id AS blocking_thread_id,
tb.processlist_user AS blocking_user,
tb.processlist_host AS blocking_host,
tb.processlist_db AS blocking_db,
tb.processlist_command AS blocking_command,
tb.processlist_state AS blocking_state,
tb.processlist_info AS blocking_info
FROM performance_schema.data_lock_waits AS w
JOIN information_schema.innodb_trx AS r
ON r.trx_id = w.requesting_engine_transaction_id
JOIN information_schema.innodb_trx AS b
ON b.trx_id = w.blocking_engine_transaction_id
LEFT JOIN performance_schema.threads AS tw
ON tw.processlist_id = r.trx_mysql_thread_id
LEFT JOIN performance_schema.threads AS tb
ON tb.processlist_id = b.trx_mysql_thread_id;
-- PROCESSLIST_ID 对应连接 ID,THREAD_ID 用于 Performance Schema 事件历史
-- 使用 LEFT JOIN 保留已经结束或暂时没有进程信息的事务行
如果 blocking_info 是空值,不要马上判定“没有阻塞 SQL”。持锁事务可能已经执行完最后一条语句,连接仍因未提交事务而持有锁;此时真正有用的是事务开始时间、事务状态和业务代码是否遗漏提交或回滚。
阻塞会话空闲时再查最近语句
当阻塞端处于空闲状态,可以用上一步得到的 blocking_thread_id 查询语句历史。这里的 THREAD_ID 不是客户端看到的连接 ID,不能把两个字段混用。
SELECT
thread_id,
event_id,
sql_text,
timer_start,
timer_end
FROM performance_schema.events_statements_history
WHERE thread_id = 28
ORDER BY event_id DESC;
-- 把 28 替换为上一条查询得到的 blocking_thread_id
-- 历史表只保留近期事件,过早的持锁语句可能已经不在里面
历史记录适合回答“这个连接刚刚做过什么”,不能证明锁现在仍由那条语句直接执行。事务提交、回滚和连接退出会改变现场,所以排查动作应该尽量靠近超时发生的时间。
用 sys.innodb_lock_waits 做快速入口
如果服务器启用了 sys schema,可以先查询 sys.innodb_lock_waits 获得更短的结果,再回到 data_lock_waits 和 data_locks 展开资源细节。
SELECT
waiting_pid,
waiting_query,
blocking_pid,
blocking_query,
locked_table,
locked_index
FROM sys.innodb_lock_waits
ORDER BY wait_age_secs DESC;
-- 先按等待时长排序,快速找出当前最久的锁等待
-- 需要更细的锁模式和事务关系时,再回到 Performance Schema 原表
这个视图适合值班时快速定位,但它仍然是对底层状态表的读取,不是持久化审计记录。若要做长期分析,应把必要字段写入自己的监控或事件表,并记录采样时间。
三个容易误判的边界
| 现象 | 更合理的解释 | 下一步 |
|---|---|---|
| blocking_query 为空 | 阻塞连接可能正在空闲,但未提交事务仍持锁 | 看 trx_started、trx_state、THREAD_ID 和语句历史 |
| 同一 waiting_trx_id 有多行 | 同一事务可能等待多个阻塞锁 | 保留每条关系,结合锁对象与模式区分 |
| 两次查询结果不一致 | 锁表是快速变化的现场快照,事务可能已提交或回滚 | 缩短采样间隔并记录采样时间,不把空结果当成历史事实 |
MySQL 官方也提醒,INNODB_TRX、data_locks、data_lock_waits 与 threads 并不是一个不可变快照。跨表连接得到的是排查当下的近似现场,不能替代事务日志或应用日志。
相关问题
锁等待超时和死锁有什么区别?
锁等待超时通常是等待时间超过配置阈值后由等待方报错;死锁则是形成环路后由 InnoDB 选择一个事务回滚。两者都可以从等待关系开始看,但死锁还应结合错误日志和死锁信息分析。
为什么只查 PROCESSLIST 看不到真正的阻塞者?
PROCESSLIST 更偏当前会话状态,未必给出 InnoDB 锁资源之间的关系。应先从 data_lock_waits 找到事务两端,再用 threads 补会话信息。
查这些表需要什么权限?
权限取决于服务器版本、账号和 Performance Schema 配置。若查询报权限错误,应由管理员按最小权限授予读取对应系统表或视图的权限,不要直接扩大到所有管理权限。
参考资料
https://dev.mysql.com/doc/refman/8.4/en/innodb-information-schema-examples.html
https://dev.mysql.com/doc/refman/8.4/en/performance-schema-data-lock-waits-table.html
https://dev.mysql.com/doc/refman/8.4/en/performance-schema-threads-table.html
-
374 收藏
-
499 收藏
-
384 收藏
-
184 收藏
-
265 收藏
-
132 收藏
-
280 收藏
-
132 收藏
-
403 收藏
-
数据库 · MySQL | 1小时前 | MySQL · 事务 · 故障排查 · mysql transaction_read_only INNODB_TRX START TRANSACTION SET TRANSACTION102 收藏
-
124 收藏
-
231 收藏
-
335 收藏
-
488 收藏
-
483 收藏
-
315 收藏
-
166 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习