立刻确认长事务是否导致UNDO积压:查SHOW ENGINE INNODB STATUS\G中HISTORY LIST LENGTH是否超10万;再执行SELECT查询运行超60秒且trx_query为NULL的活跃事务,结合PROCESSLIST定位卡住的应用连接。

长事务不提交,Undo Log 就不会被 purge,磁盘空间会持续上涨,直到实例被自动锁定——这不是预警,是已经发生的事实。
怎么立刻确认是不是长事务撑爆了磁盘
别先看 df -h 或 RDS 控制台的“空间使用率”,先查数据库内部是否已积压大量未清理的 undo:
- 运行
SHOW ENGINE INNODB STATUS\G,找到HISTORY LIST LENGTH行——如果超过 10 万,基本可以断定 purge 被阻塞 - 执行
SELECT trx_id, trx_started, trx_state, trx_mysql_thread_id, TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) AS duration_sec, trx_query FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60;,重点看duration_sec和trx_state = 'RUNNING'但trx_query IS NULL的记录(空闲中、没提交) - 对比
information_schema.PROCESSLIST中对应ID的USER、HOST、INFO,确认是不是某个应用连接卡住了(比如 Python 进程异常退出但连接没 close)
kill 长事务前必须判断的三件事
直接 KILL [thread_id] 很快,但可能让情况更糟:
- 查
trx_rows_modified字段(来自INNODB_TRX):如果大于 10 万,回滚本身可能耗时数分钟甚至更久,期间仍占 undo 空间、拖慢 purge - 确认该线程归属服务:用
PROCESSLIST.HOST和USER定位到具体应用,优先联系负责人——有些“长事务”其实是业务逻辑必需(如导出报表前预锁),贸然 kill 会导致数据不一致 - 检查当前
innodb_purge_threads是否为 1(默认值)且innodb_max_purge_lag设得过高(比如 > 1000000):这会让 purge 主动降速,加剧积压,不是靠 kill 能解决的
清理后磁盘空间还不下降?Undo 文件没释放
即使 kill 了所有长事务,ibdata1 或独立 undo_001 文件也不会自动收缩——InnoDB 不支持 online shrink undo 表空间:
- 若使用共享表空间(
innodb_undo_tablespaces = 0),唯一办法是 mysqldump 全库重建,风险高、停机长 - 若已启用独立 undo 表空间(
innodb_undo_tablespaces > 0),可尝试SET GLOBAL innodb_undo_log_truncate = ON;,但前提是HISTORY LIST LENGTH已降到很低( - 检查
innodb_undo_log_truncate是否生效:查SHOW VARIABLES LIKE 'innodb_undo_log_truncate';和SHOW STATUS LIKE 'Innodb_undo_log_truncated';
为什么改隔离级别能缓解 undo 压力
REPEATABLE READ 是 MySQL 默认隔离级别,但它会让事务第一次 SELECT 创建的 read view 持续有效,导致旧版本必须保留到该事务结束;而 READ COMMITTED 每次 SELECT 都新建 read view,purge 线程能更早清理不再需要的 undo 版本链节点:
- 对只读或弱一致性要求的业务模块(如后台统计查询),可在会话级临时切换:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; - 注意:不能全局设成
READ COMMITTED后就不管了——某些依赖可重复读语义的业务逻辑(如两次 SELECT 中间 UPDATE)会出错 - 这个调整治标不治本,必须配合长事务监控和应用层事务边界收敛(比如把一个 5000 行的
UPDATE拆成每批 500 行)
真正难处理的从来不是“怎么 kill”,而是那些 trx_state = 'ROLLING BACK' 却卡住不动的线程——它已经无法响应 KILL,只能等,或者重启实例。这时候 undo 空间是否可回收,取决于 purge 线程能否推进,而 purge 又被它自己阻塞着。这种死锁态,往往意味着底层 I/O 已严重受限或 buffer pool 几乎全被旧版本占满。


















