应查innodb_trx中trx_state='running'且trx_query为空、trx_started超30秒(生产建议120秒)的线程,其本质是存储过程开启事务后未commit/rollback而挂起;须结合processlist确认info为call或为空、state非sleep,并通过innodb_lock_waits定位blocking_pid后kill对应trx_mysql_thread_id。

查 INNODB_TRX 里状态为 RUNNING 且 trx_query 为空的存储过程线程
存储过程调用后卡住,最常见的情况是它内部显式或隐式开启了事务(比如含 BEGIN 或 START TRANSACTION),但没走到 COMMIT 或 ROLLBACK 就挂了。此时 INFORMATION_SCHEMA.INNODB_TRX 中该事务的 TRX_STATE 仍为 RUNNING,TRX_QUERY 却为空——这不是“没在执行”,而是“已执行完语句,但事务没收口”。
执行这条语句快速筛出可疑线程:
SELECT TRX_ID, TRX_MYSQL_THREAD_ID, TRX_STARTED, TRX_STATE, SUBSTRING_INDEX(TRX_QUERY, ' ', 5) AS short_query FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TRX_STATE = 'RUNNING' AND TIME_TO_SEC(TIMEDIFF(NOW(), TRX_STARTED)) > 30 AND (TRX_QUERY IS NULL OR TRX_QUERY = '');
- 别只看
PROCESSLIST里的State = 'Sleep':很多挂起的存储过程线程就停在这儿,TIME很大但INFO是空或只有CALL proc_name() -
TRX_STARTED时间戳比当前早 30 秒以上,基本可判定为异常滞留;生产环境建议阈值设为 120 秒 - 如果
TRX_QUERY非空,优先看它最后一句是不是UPDATE或SELECT FOR UPDATE—— 那可能是死锁前正在执行的语句,不是开头那句
用 blocking_pid 定位真正该 kill 的线程,而不是报错的那个
当其他查询报 Lock wait timeout exceeded 或显示 Waiting for table metadata lock,错误日志或应用层看到的线程 ID 往往只是“被堵的”,不是“堵路的”。真凶藏在锁等待链里。
MySQL 8.0+ 直接查系统视图:
SELECT waiting_pid, blocking_pid, object_schema, object_name, index_name FROM performance_schema.data_lock_waits;
MySQL 5.7 及之前用:
SELECT r.trx_mysql_thread_id AS waiting_trx_id,
b.trx_mysql_thread_id AS blocking_trx_id,
r.trx_query AS waiting_query,
b.trx_query AS blocking_query
FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS w
JOIN INFORMATION_SCHEMA.INNODB_TRX b ON b.trx_id = w.blocking_trx_id
JOIN INFORMATION_SCHEMA.INNODB_TRX r ON r.trx_id = w.requesting_trx_id;
- 必须
KILLblocking_pid对应的线程 ID,不是waiting_pid:杀错会导致下一个请求立刻复现阻塞 -
blocking_query为空不等于安全——可能刚执行完UPDATE就卡在应用层 HTTP 调用里了,事务仍活跃 - 特别留意
User = 'system user'(主从同步线程)或定时任务用户,误杀会引发主从延迟甚至数据不一致
确认该线程是否来自存储过程:靠 PROCESSLIST + 时间戳对齐
SHOW ENGINE INNODB STATUS 输出的 SQL 文本里不会带调用栈,所以仅凭 TRX_QUERY 无法 100% 确认是否在存储过程中。得交叉验证。
先拿到上一步查出的 blocking_pid(即 TRX_MYSQL_THREAD_ID),再查:
SELECT ID, USER, HOST, COMMAND, TIME, STATE, INFO FROM INFORMATION_SCHEMA.PROCESSLIST WHERE ID = ?;
- 如果
INFO是CALL my_proc(...)或为空,而STATE是Execute或Sleep、TIME很大,基本可断定是存储过程内部卡住 - 如果
INFO显示的是某条UPDATE,但该语句本身逻辑简单,就要怀疑它是不是被包裹在某个CALL里执行的——这时必须结合应用日志,按同一thread_id和时间点对齐调用记录 - 开发期可在存储过程中加日志:比如写入临时表
debug_log或用SIGNAL SQLSTATE '01000' SET MESSAGE_TEXT = 'at step 3';,提前埋点能大幅缩短定位时间
别碰 innodb_lock_wait_timeout,先看 autocommit 设置
把 innodb_lock_wait_timeout 从 50 改成 300,只会让失败来得更晚,不会释放锁。真正该查的是事务起点是否可控。
执行:
SELECT @@autocommit, @@global.autocommit;
- 如果返回
0,说明连接默认不自动提交,所有 DML 都在隐式事务里累积——哪怕只执行一条SELECT,只要没COMMIT,MDL 读锁就一直挂着,DDL 必然阻塞 - 很多 ORM(如 Spring)默认开启事务管理,但若方法里混了 RPC 调用又没设超时,就会导致事务长时间悬停;
@Transactional方法内禁止跨服务调用,是硬边界 - 临时救急可用
SET SESSION innodb_lock_wait_timeout = 5:让阻塞快速暴露,逼出隐藏长事务,比调大更有诊断价值
最常被忽略的一点:事务即使只做了一次 SELECT 并加了 FOR UPDATE,只要没 COMMIT,行锁和 MDL 锁都还在——这不是性能问题,是锁协议本身的刚性约束。











