长时间running且trx_query为空的事务大概率是未提交的悬挂事务,表现为trx_state为running、trx_started远早于当前时间、trx_query为null,即“幽灵锁源”。

查INNODB_TRX里长时间RUNNING且trx_query为空的事务
这类事务大概率开了事务但没提交,正空挂着占锁。它不执行SQL,所以trx_query是NULL,但trx_state是RUNNING,trx_started时间远早于当前——这是最典型的“幽灵锁源”。
运行这条语句快速筛出可疑项:
SELECT trx_id, trx_mysql_thread_id, trx_started, trx_state, trx_query FROM information_schema.INNODB_TRX WHERE trx_state = 'RUNNING' AND trx_started
- 重点关注
trx_started比现在早1分钟以上的记录; -
trx_mysql_thread_id可直接用于后续KILL; - 若应用用了连接池,
trx_mysql_thread_id常对应一个长期空闲却未关闭的连接; - Spring中
@Transactional方法里调了外部服务(如HTTP、RPC)且没设超时,就容易卡在这里。
联查INNODB_LOCK_WAITS确认谁在等、谁在堵
光看持有者不够,得知道哪条SQL被卡住、被谁卡住。MySQL 5.7用INNODB_LOCK_WAITS,8.0+建议用sys.innodb_lock_waits(字段更直观)。
执行这个联查,一次看清等待链:
MySQL 9.6.0是面向Linux平台的2026年创新版本,核心架构迎来重大革新。其将外键约束与级联操作从InnoDB引擎层上移至SQL层,确保所有数据变更均被完整记录至Binlog,彻底解决了CDC(变更数据捕获)与主从复制中的数据不一致难题。此外,该版本引入container_aware启动选项以原生适配容器环境,并对审计日志进行了组件化重构,为追求极致数据一致性与云原生体验的开发者提供了全新选择。
SELECT r.trx_id waiting_trx_id, r.trx_query waiting_query,
b.trx_id blocking_trx_id, b.trx_query blocking_query,
b.trx_started blocking_started,
TIMESTAMPDIFF(SECOND, b.trx_started, NOW()) blocking_sec
FROM information_schema.INNODB_TRX r
INNER JOIN information_schema.INNODB_LOCK_WAITS w ON r.trx_id = w.requesting_trx_id
INNER JOIN information_schema.INNODB_TRX b ON b.trx_id = w.blocking_trx_id;
- 输出中
blocking_query为空或明显异常(比如SLEEP(300)、空循环、阻塞I/O),就是根因; - 别杀
waiting_trx_id——那是受害者,杀它只是让错误重来一遍; - 注意
blocking_started时间,如果超过几分钟,基本可断定是应用逻辑缺陷而非瞬时竞争; - MySQL 8.0+中
blocking_pid比blocking_trx_id更易关联SHOW PROCESSLIST。
用SHOW PROCESSLIST匹配trx_mysql_thread_id找真实线程状态
INNODB_TRX里的trx_mysql_thread_id和SHOW PROCESSLIST的ID是一一对应的。很多“未提交事务”其实在PROCESSLIST里表现为Sleep状态,Time值很大,但你容易忽略它。
执行:
SHOW PROCESSLIST;
- 找出
State为Sleep、Time> 60 的行; - 比对它的
ID是否在上一步INNODB_TRX结果的trx_mysql_thread_id中; - 如果匹配上,且
Info列为空,基本坐实:这个连接开了事务、干了点事、然后挂起了; - 特别留意
User字段为system user的线程——这是复制线程,千万别KILL。
别急着调innodb_lock_wait_timeout
把innodb_lock_wait_timeout从50秒改成300秒,不会让锁消失,只会让报错延迟出现。它掩盖问题,不解决问题。
- 临时调低(如
SET SESSION innodb_lock_wait_timeout = 5)反而有用:能更快暴露隐藏的长事务; - 全局修改需重启或
SET GLOBAL,新连接才生效,生产环境改了等于没改; - 如果调大后错误变少,说明你的应用层事务边界没理清——比如DAO层手动
BEGIN却忘了COMMIT; - 索引缺失导致全表扫描进而升级为表锁时,调timeout完全无效,必须加索引。
真正难的不是查到那个trx_mysql_thread_id,而是顺着它找到对应的应用代码里哪一行beginTransaction()后面缺了commit()或rollback()——尤其是带异常分支的路径。










