必须先终结最老活跃事务,否则所有调参和监控都只是隔靴搔痒;真正卡住purge的是trx_state='running'且trx_query is null的幽灵事务,需用innodb_trx定位并谨慎kill。

必须先终结最老活跃事务,否则所有调参和监控都只是隔靴搔痒。
查真凶:只信 INNODB_TRX,别看 PROCESSLIST
真正卡住 purge、拖垮性能的,不是正在执行 SQL 的事务,而是 trx_state = 'RUNNING' 且 trx_query IS NULL 的“幽灵事务”——它早已执行完,却没提交,还在死死攥着 undo 快照。
- 用这条语句抓它:
SELECT trx_id, trx_started, TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) AS duration_sec, trx_state, trx_rows_modified, trx_query FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 600; -
duration_sec > 600且trx_rows_modified > 0:写事务悬停,优先处理 -
trx_rows_modified = 0但duration_sec很大:只读事务长期持快照,同样会阻塞 purge,不能忽略 -
TRX_MYSQL_THREAD_ID可关联information_schema.PROCESSLIST.ID查来源 IP 和用户,但注意线程可能已断开,TRX_STATE仍为RUNNING
KILL 前必须看状态,乱杀会让回滚更慢
KILL 不是万能解药,错杀可能让 IO 爆表、回滚时间翻倍。动手前必须确认 trx_state:
-
trx_state = 'RUNNING'且trx_query IS NULL:大概率是应用漏了COMMIT或连接池未close,可安全KILL -
trx_state = 'LOCK WAIT':先查INNODB_LOCK_WAITS找 blocking 事务,干上游 -
trx_state = 'ROLLING BACK':别动,此时KILL只会让回滚更久 - 如果
SHOW ENGINE INNODB STATUS\G中History list length> 10000,说明 purge 已严重滞后,KILL要加SLEEP(0.2)间隔执行,避免雪崩
innodb_undo_log_truncate 不是清理命令,只是个开关
开了这个参数但磁盘还在涨?不是没生效,是长事务还在源源不断地生成新 undo,旧的 purge 不掉。
- 它只对独立 undo 表空间(
undo_001等)生效,必须先确保innodb_undo_tablespaces >= 4(MySQL 5.7+),否则直接跳过 - 它不是实时触发,而是后台线程每 128 秒检查一次,且前提是长事务已结束、
History list length降到阈值以下 - 别碰
ibdata1里的共享 undo:截断无效,只能重建实例 - 配合加大 purge 能力:
SET GLOBAL innodb_purge_batch_size = 10000;,SET GLOBAL innodb_purge_rseg_truncate_frequency = 16;
应用层超时比服务端参数更关键
innodb_lock_wait_timeout 控制锁等待超时,不控制事务存活时间;wait_timeout 断空闲连接,但无法覆盖已开启事务却卡在应用层的场景。
- Java:用
@Transactional(timeout = 30)显式设事务级超时 - Python SQLAlchemy:设置
execution_options={'timeout': 30} - 禁止在事务内做 HTTP 调用、
sleep()、文件读写等外部耗时操作 - 连接池必须启用泄漏检测:
leak-detection-threshold(HikariCP)或removeAbandonedOnBorrow(DBCP),阈值建议设为 60 秒
最容易被忽略的是那种“已开始、无 SQL 正在执行、也不响应 KILL”的事务——它往往卡在应用网络层或死锁检测间隙,这时候数据库侧已无解,只能靠业务定位并重启对应服务实例。











