长事务卡住Purge线程是因为其未提交导致全局最老Read View的min_trx_id被钉住,使所有更新的undo log无法清理;尤其trx_state='RUNNING'且trx_query IS NULL的空闲事务危害最大。

长事务为什么卡住Purge线程
Purge线程不是“不干活”,而是被明确禁止清理某些undo log——只要还有活跃事务的Read View里包含某个trx_id,对应的历史版本就必须保留。长事务没提交,它的Read View就一直有效,所有它开启后产生的旧版本都成了“钉子户”,purge线程只能绕着走。
关键点在于:InnoDB的Purge清理依据是全系统当前最老活跃Read View中的min_trx_id。这个值由所有未提交事务中最小的trx_id决定。哪怕一个事务只执行了SELECT、没改任何数据、trx_rows_modified = 0,只要它没提交,min_trx_id就被钉住,所有比它新的undo记录全部无法回收。
-
REPEATABLE READ下,事务第一次快照读即生成固定Read View,后续复用——长事务一开就“锚定”清理边界 -
READ COMMITTED下每次查询都新建Read View,理论上缓解阻塞,但高并发时仍可能短暂拖慢进度 -
SHOW ENGINE INNODB STATUS\G里看到PURGE DONE for trx's n:o 卡在低值不动,就是min_trx_id被钉死的直接证据
最危险的信号:trx_state = 'RUNNING' 且 trx_query IS NULL
这种事务早已执行完SQL,但既没COMMIT也没ROLLBACK,连接还挂着。它不产生新undo,却持续挡在purge路径上,导致History list length只增不减。
这类事务在SHOW PROCESSLIST里Time字段往往为0或很小,容易被忽略;而information_schema.INNODB_TRX里却暴露无遗:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
SELECT trx_id, trx_started, trx_state, trx_query, trx_mysql_thread_id FROM information_schema.INNODB_TRX WHERE trx_state = 'RUNNING' AND trx_query IS NULL ORDER BY trx_started LIMIT 1;
- 查出来后必须关联
information_schema.PROCESSLIST确认HOST和USER,排除监控/备份等合法长连接 - 大概率是Python/Java应用崩溃后连接未释放,或ORM事务未正确关闭
- 只要
trx_started超过600秒,优先KILL——比调任何参数都管用
别信innodb_max_purge_lag,它只是刹车片
innodb_max_purge_lag设再小,也不会让purge线程多干一点活;它只在History list length超阈值时,给新DML加usleep延迟。本质是用写入变慢换空间安全,治标不治本。
- 设为0:等于关闭保护,磁盘爆满风险直线上升
- 设为500000:较平衡,但前提是没长事务堵着——否则
history list永远卡在10万以上 - 真正起效的前提:先清掉最老的
trx_state = 'RUNNING'且trx_query IS NULL事务
杀完之后Purge仍不推进?你漏了三件事
杀掉源头不等于问题结束——History list length不会立刻下降,得主动助推:
- 检查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',值必须≥4(推荐),并确保innodb_undo_log_truncate = ON - 再执行:
ALTER UNDO TABLESPACE undo_001 TRUNCATE
最容易被忽略的是:Purge滞后从来不是参数问题,而是那个trx_query IS NULL却挂着超过10分钟的事务ID。它不产生新undo,却持续挡路——定位它、杀掉它,比翻文档调十次参数都直接。










