长事务是Undo Log膨胀和性能衰减的直接源头;必须定位并终止trx_state = 'RUNNING'且trx_query IS NULL的空闲未提交事务,再结合purge调优与应用层治理根治问题。

长事务是Undo Log膨胀和性能衰减的直接源头,所有参数调优、purge加速都只是缓释手段;必须先定位并终止真正卡住purge的事务,否则任何配置调整都是徒劳。
查真凶:用INNODB_TRX揪出空闲但未提交的事务
别信SHOW PROCESSLIST里的Time字段——它只反映当前语句执行时长。真正钉死undo的,是那些trx_state = 'RUNNING'且trx_query IS NULL的事务。它们早已执行完,却没COMMIT或ROLLBACK,导致purge线程无法清理对应旧版本。
- 运行这条语句找最老的活跃事务:
SELECT trx_id, trx_started, trx_state, trx_rows_modified, trx_mysql_thread_id FROM information_schema.INNODB_TRX ORDER BY trx_started LIMIT 5 - 重点关注
trx_started超10分钟、trx_rows_modified = 0(只读)或极大(写多)、且trx_query为空的记录 - 用
trx_mysql_thread_id关联information_schema.PROCESSLIST,确认HOST、USER、COMMAND和INFO,判断是否是异常挂起的应用连接
KILL前必须分清状态:不是所有长事务都能直接杀
盲目KILL可能让回滚更慢、IO更满,甚至拖垮purge线程。不同trx_state要区别对待:
-
trx_state = 'RUNNING'且trx_query IS NULL:极大概率是应用层漏了COMMIT,可安全KILL对应thread_id -
trx_state = 'LOCK WAIT':它被别的事务堵住了,先查INNODB_LOCK_WAITS找出blocking_trx_id,优先干掉上游 -
trx_state = 'ROLLING BACK':别动,此时KILL只会延长回滚时间、加剧IO压力 - 若
trx_rows_modified > 100000,回滚可能耗时数分钟,KILL前务必评估业务影响
促排:杀完源头后主动推一把purge进度
杀掉长事务后,History list length不会立刻下降,需手动助推purge线程:
- 检查purge进度:
SHOW ENGINE INNODB STATUS\G,关注“PURGE DONE for trx's n:o < XXX”行,对比trx_id看是否在推进 - 临时加大清理力度:
SET GLOBAL innodb_purge_batch_size = 10000(默认300) - 确认已启用独立undo表空间:
SHOW VARIABLES LIKE 'innodb_undo_tablespaces',值必须≥2(推荐4) - 开启自动截断:
SET GLOBAL innodb_undo_log_truncate = ON,再对每个undo表空间执行:ALTER UNDO TABLESPACE undo_001 TRUNCATE
防复发:应用层才是根治点,数据库层只是兜底
所有数据库参数调整都解决不了源头问题。Undo膨胀的根因永远在应用逻辑里:
- 禁止在事务内做HTTP调用、文件读写、
SLEEP等不可控延迟操作 - 批量更新必须分页,单次事务控制在1000行以内,每批后显式
COMMIT - ORM框架(如MyBatis、Django)要显式配置
autocommit=true,避免隐式事务跨请求 - 连接池(如HikariCP)中未显式控制事务边界,极易造成“假空闲真持锁”状态
物理undo文件不收缩是正常现象,关键在于阻断持续膨胀。只要History list length ≤ 100且稳定,就说明purge已跟上节奏。监控不能只靠人工,必须固化成告警:运行超300秒的事务数>3,或History list length > 50000,就得立刻介入。



















