登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  MySQL

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 映射到会话。

MySQL data_lock_waits、data_locks 与 innodb_trx 的锁等待关系结构图
图1:锁等待关系结构图,展示等待事务、阻塞事务、锁资源和事务语句之间的静态对应关系,不是运行截图。

第一条查询:同时找出等待者和阻塞者

先执行下面这条最小查询。它直接沿着官方文档给出的关系,把同一条等待记录的两端事务展开。字段名刻意保留 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。用前者关联,后者才能继续查询语句历史。

MySQL 事务连接 ID 映射到 performance_schema.threads 会话信息的结构图
图2:阻塞会话映射结构图,展示事务、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

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>