必须先查innodb_trx定位最老活跃事务(trx_state='running'且trx_query is null),再新增undo表空间、设旧表空间为inactive,待history list length下降后执行truncate;跳过任一环节或仅调参均会失败。

innodb_max_purge_lag 或只开 innodb_undo_log_truncate 都会失败。
怎么快速定位正在拖垮 purge 的长事务
History list length 持续上涨,说明 purge 线程被卡住——但真正卡住它的,永远是那个最早启动却还没提交的事务。别猜,直接查:
SELECT trx_id, trx_state, trx_started, TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) AS duration_sec, trx_mysql_thread_id, trx_query FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 600;- 重点关注
trx_state = 'RUNNING'且trx_query IS NULL:这是空闲连接没 commit,最常见也最危险 - 搭配
SELECT ID, USER, HOST, COMMAND, TIME, INFO FROM information_schema.PROCESSLIST WHERE ID = ?;查线程来源和实际执行语句 - TRX_ROWS_MODIFIED > 10000 且 duration_sec > 300 的 UPDATE/DELETE 必须立刻干预
为什么不能直接 ALTER UNDO TABLESPACE ... TRUNCATE
因为 MySQL 8.0 要求至少保留 2 个 active 的 undo 表空间,且 truncate 前必须确保 purge 已跟上、无活跃依赖。常见失败现象:
-
ERROR 3655 (HY000): Cannot set innodb_undo_001 inactive since there would be less than 2 undo tablespaces left active—— 默认只有 2 个,不够轮换 -
ALTER UNDO TABLESPACE undo_001 SET INACTIVE成功后,TRUNCATE仍卡住:说明 History list length 未回落,purge 还没清理完旧段 - 手动删文件或改
ibdata1里的 undo:完全无效,还会导致实例无法启动
必须先新增第 3 个 undo 表空间,再设旧的为 inactive,等 purge 推进后再 truncate。
如何安全收缩已膨胀的 undo_001 或 undo_002
收缩不是删除,是让 MySQL 自动回收空间。整个流程必须按顺序执行,跳步必失败:
- 确认参数已启用:
SET GLOBAL innodb_undo_log_truncate = ON;(默认已开,但需确认) - 新增 undo 表空间:
CREATE UNDO TABLESPACE undo_003 ADD DATAFILE '/var/lib/mysql/undo_003.ibu';(注意后缀必须是.ibu) - 设原表空间为 inactive:
ALTER UNDO TABLESPACE innodb_undo_001 SET INACTIVE;(此时仍有 2 个 active:undo_002 + undo_003) - 等待
SHOW ENGINE INNODB STATUS\G中 History list length 显著下降(通常需数秒至数分钟) - 触发截断:
ALTER UNDO TABLESPACE innodb_undo_001 TRUNCATE;—— 成功后文件大小会回落到初始值(约 16MB)
注意:innodb_max_undo_log_size 控制截断后目标大小(默认 1GB),但首次截断后实际大小由系统决定,通常为 16MB。
哪些操作看似合理实则危险
很多 DBA 在 panic 状态下会做错三件事:
- 把
innodb_max_purge_lag设成 0:等于关闭写入保护,undo 继续疯涨,磁盘爆满只是时间问题 - 在未 kill 长事务前就执行
TRUNCATE:命令会 hang 住,阻塞所有 DML,实例进入半死状态 - 修改
innodb_undo_tablespaces从 2 改为 1:MySQL 不允许减少数量,只会忽略,且不报错 - 误以为
innodb_purge_threads = 1就够了:高并发下建议设为 4,否则 purge 永远追不上写入
最易被忽略的一点:应用层事务控制比任何数据库参数都关键。一个 HTTP 调用卡在事务里 5 分钟,所有调参都是给它擦屁股。











