先止血(终止长事务),再促排(加大purge_batch_size、启用undo截断),最后防复发(应用层禁http调用、分页提交、连接池优化);物理文件不收缩属正常,history list length≤100即稳定。

核心思路是:先止血(终止源头长事务),再促排(加速purge清理),最后防复发(监控+应用改造)。物理文件不收缩是正常现象,关键在阻断持续膨胀。
第一步:精准定位真正卡住purge的长事务
别信 SHOW PROCESSLIST 的 Time 字段——它只算当前语句执行时长。真正钉住 undo 的,是那些 trx_state = 'RUNNING' 且 trx_query IS NULL 的空闲事务。它们早已执行完,却没提交,导致 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 不是万能解药,不同状态要区别对待:
- trx_state = 'RUNNING' 且 trx_query IS NULL:极大概率是应用未调 COMMIT/ROLLBACK,可安全 KILL 对应 thread_id
- trx_state = 'LOCK WAIT':它被别的事务堵住了,先查 INNODB_LOCK_WAITS 找出 blocking_trx_id,优先干掉上游
- trx_state = 'ROLLING BACK':别动,此时 KILL 只会让回滚更慢、更占 IO,甚至拖垮 purge 线程
- 若 trx_rows_modified > 100000,回滚可能耗时数分钟,KILL 前务必评估业务影响和恢复时间
第三步:释放 purge 压力,推动历史 undo 清理
杀完源头后,history list length 不会立刻下降,需主动助推 purge:
- 检查 purge 进度:SHOW ENGINE INNODB STATUS\G,关注 “PURGE DONE for trx's n:o
- 临时加大清理力度: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;
第四步:根治措施——从应用层切断膨胀源头
所有数据库参数调整都是兜底,真正治本在代码和架构:
- 禁止在事务内做 HTTP 调用、文件读写、sleep、复杂计算等耗时操作
- 批量更新必须分页,单次事务控制在 1000–5000 行,每批后显式 COMMIT
- ORM 框架(如 MyBatis、Django)务必配置 autocommit=true,防止隐式事务跨请求
- 连接池配置合理 idleTimeout 和 maxLifetime,避免连接空闲挂起却不释放事务
- 上线前强制扫描含 BEGIN / START TRANSACTION 的代码,确保每处都有对应 COMMIT 或 ROLLBACK
物理文件 ibdata1 或 undo_001 不会自动缩小,这是 InnoDB 设计使然。只要 history list length 回落到 100 以下并稳定,说明 purge 已跟上节奏,膨胀就止住了。后续靠监控固化:超 5 分钟的事务数 > 3 就告警,history list length > 50000 就升级人工介入。











