undo log空间不足本质是历史版本堆积与purge阻塞导致的保护性拦截,触发条件为history list length持续超5000且独立undo表空间处于active增长状态;根本解法是定位并终止阻塞事务,而非调参或删数据。

Undo Log空间不足不是“空间真的没了”,而是InnoDB发现历史版本堆积太多、purge线程跟不上,主动拒绝新写入——它是在保护自己不被拖垮。
Undo Log空间不足的真正触发条件
MySQL不会等到磁盘写满才报错,而是在内部判定“undo清理已严重滞后”时就拦截新DML。关键判断依据有两个:
-
HISTORY LIST LENGTH持续高于5000(尤其上万),说明未purge的undo记录积压严重 - 独立undo表空间(如
undo_001)状态为ACTIVE且文件持续增长,但innodb_undo_log_truncate无法生效(因有长事务阻塞)
此时即使磁盘剩余空间充足,也会报InnoDB: Undo tablespace is full或Transaction can't be committed。
长事务如何卡死Undo清理链路
一个未提交的事务会把最老的read view钉住,导致所有早于它的undo记录都不能被purge——哪怕那些事务早已提交。后果是:
- 新事务产生的undo只能追加,不能复用旧空间
-
innodb_purge_threads线程空转,因为purge起点被锁死 - 即使你
KILL掉该事务,回滚过程本身又会生成新undo,并继续占用空间数分钟
典型场景:Python应用执行完UPDATE后忘了commit(),连接挂起;或Java服务开启事务后调用外部HTTP接口超时,事务卡在中间状态。
innodb_max_purge_lag只是刹车,不是修车工
设innodb_max_purge_lag = 100000只会让新INSERT/UPDATE变慢,但它不做三件事:
- 不终止正在运行的长事务
- 不加速现有undo的purge进度
- 不释放已被占用的undo表空间文件(
ibdata1或undo_001)
错误做法是把它设成0试图“强制清空”,结果MVCC读取大量触发Lock wait timeout exceeded,业务查询大面积失败。
为什么删数据、重启、调大innodb_undo_tablespaces都没用
这些操作治标不治本:
- 删表或
TRUNCATE不释放undo空间——它们只删数据页,undo段仍被长事务引用 - 重启MySQL后,如果长事务还在(比如应用重连复用旧连接),问题立刻复现
-
innodb_undo_tablespaces调大只是多划几块地,没解决“地里堆满垃圾没人清”的问题
真正要动的是事务生命周期本身:找到trx_mysql_thread_id,确认业务是否允许kill,再决定是让它回滚还是协调应用侧修复逻辑。
最容易被忽略的一点:REPEATABLE READ隔离级别下,一个简单SELECT也会延长undo保留时间——如果你的报表脚本开着事务查完100万行才commit,它就在后台默默吃掉几十MB undo空间。











