真正要查的是“开了很久却没提交”的空挂事务,需筛选INNODB_TRX中trx_state='RUNNING'、trx_query为空且trx_started早于当前60秒的记录,并关联PROCESSLIST确认Sleep状态下的幽灵连接及blocking_thread_id后KILL。

查INNODB_TRX找长时间RUNNING且trx_query为空的事务
真正卡住别人的,往往不是正在跑SQL的线程,而是开了事务却什么都没执行、就挂着不动的“空挂事务”。这类事务在INNODB_TRX里表现为trx_state = 'RUNNING'、trx_query为NULL或空字符串,且trx_started时间远早于当前(比如超过60秒)。它们极大概率是应用异常中断、RPC超时未处理、或@Transactional方法里catch了异常却忘了rollback。
运行这条语句快速筛出可疑目标:
SELECT trx_id, trx_mysql_thread_id, trx_started, trx_state, trx_query, trx_isolation_level
FROM information_schema.INNODB_TRX
WHERE trx_state = 'RUNNING'
AND trx_started <p>重点关注<code>trx_mysql_thread_id</code>字段值,它就是后续要关联确认和可能KILL的线程ID。</p><h3>用INNODB_LOCK_WAITS确认谁在堵谁</h3><p><code>INNODB_TRX</code>只告诉你“有长事务”,但不确定它是否真在阻塞别人。必须通过<code>INNODB_LOCK_WAITS</code>建立等待链路——它会明确告诉你哪个事务在等哪个事务。</p><p>执行以下查询:</p><pre class="brush:php;toolbar:false;">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_mysql_thread_id blocking_thread_id
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;
如果返回结果为空,不代表没锁:可能是锁刚释放、或已触发死锁自动回滚(此时SHOW ENGINE INNODB STATUS\G末尾会有LATEST DETECTED DEADLOCK段落);如果有结果,blocking_query为空或只是简单SELECT,基本可断定是事务没提交导致锁滞留。
联查PROCESSLIST确认Sleep状态下的幽灵连接
很多阻塞源在SHOW PROCESSLIST里看起来毫无威胁:COMMAND = 'Sleep'、STATE = ''、INFO为空、但TIME高达几百甚至上千秒。这种“幽灵连接”不占CPU、不打日志,却持续持有行锁或间隙锁。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
用这个语句直接定位:
SELECT p.ID, p.USER, p.HOST, p.DB, p.COMMAND, p.TIME, p.STATE, p.INFO,
t.trx_id, t.trx_started, t.trx_state
FROM information_schema.PROCESSLIST p
JOIN information_schema.INNODB_TRX t ON p.ID = t.trx_mysql_thread_id
WHERE p.COMMAND = 'Sleep'
AND t.trx_state = 'RUNNING'
AND p.TIME > 300
ORDER BY t.trx_started;
关键判断点:
-
HOST和USER能帮你快速圈定是哪个应用实例或开发终端连上来的 - 别碰
USER = 'system user'的线程,那是主从同步线程,KILL会导致数据不一致 -
TIME > 300且INFO为空 → 大概率是连接池归还了连接但没清理事务上下文
KILL blocking_thread_id,不是报错线程ID
报错语句本身只是第一个等不及的受害者,KILL它只会让当前请求失败,阻塞源还在继续霸占锁。必须KILL的是blocking_thread_id(来自上一步INNODB_LOCK_WAITS或PROCESSLIST联查结果)。
执行:
KILL <blocking_thread_id>;</blocking_thread_id>
注意:
- MySQL 5.7中
KILL后事务会回滚,锁立即释放,但需确认该事务无副作用(比如它正处在资金扣减中途) - 若
blocking_query是SLEEP(300)或调外部HTTP接口,说明是应用逻辑缺陷,不是临时应急能解决的 - 线上环境建议先
SELECT确认线程归属,再KILL;避免误杀定时任务或监控采集连接
最棘手的情况,是那个持锁事务既不执行SQL、也不报错、trx_query为空、PROCESSLIST里COMMAND = 'Sleep'、TIME持续增长——它安静得像不存在,却能让所有写请求排队超时。这种事务一旦漏掉,问题就会反复出现。










