必须先杀掉trx_state='RUNNING'且trx_query IS NULL的长事务,否则所有调参、截断、purge加速均无效;需通过INNODB_TRX定位最老事务,关联PROCESSLIST确认来源后按状态分类KILL,再加大innodb_purge_batch_size并启用undo自动截断。

事务嵌套未正常提交不会导致传统意义的“内存泄漏”,但会引发连接长期占用、undo日志持续膨胀、INNODB_TRX 表数据堆积,最终表现为 MySQL RSS 内存缓慢上涨、created_tmp_disk_tables 异常升高、甚至触发 OOM Killer——本质是事务生命周期失控,而非 malloc 未释放。
查 INNODB_TRX 里卡住的嵌套事务状态
嵌套事务(如 SAVEPOINT 后未 ROLLBACK TO 或 RELEASE SAVEPOINT,或外层事务未 COMMIT)在 MySQL 中并不真正“嵌套”,而是延续同一个事务上下文。关键看它是否处于悬停态:
-
TRX_STATE = 'RUNNING'且TRX_QUERY IS NULL:事务已执行完 SQL,但没提交,连接还绑在线程上 -
TRX_STARTED时间远早于当前(比如 >5 分钟),且TRX_ROWS_MODIFIED > 0:大概率是外层事务开启后,中间调用某段逻辑(如异步回调、手动getConnection())导致流程提前退出,忘了commit() - 配合
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TRX_STATE = 'RUNNING' ORDER BY TRX_STARTED LIMIT 10;快速定位最老的几个活跃事务
确认是否由编程式事务 + 提前 return 导致
@Transactional 注解无法覆盖手动获取的连接,一旦在事务方法内调用 dataSource.getConnection(),再加个 if (xxx) return;,这个裸连接就永远卡在 RUNNING 状态。
- 搜索应用代码中所有出现
getConnection()、doInTransaction、TransactionCallbackWithoutResult的地方 - 重点检查是否有
try { ... if (cond) return; } finally { conn.close(); }—— 这种写法无效,return会跳过finally里的close(),更别说commit() - 正确做法必须显式控制终态:
try { ... } catch (e) { conn.rollback(); } finally { conn.close(); },且不能在 try 块里直接 return
观察 undo 日志与临时表内存增长关联性
长事务不提交,InnoDB 就不能 purge undo log,会导致 innodb_undo_log_truncated 不生效、innodb_history_list_length 持续上升,间接推高 buffer pool 和系统内存压力。
- 执行
SHOW ENGINE INNODB STATUS\G,搜History list length:值大于 10000 就危险,超过 50000 很可能已拖慢 purge 线程 - 查指标
SHOW GLOBAL STATUS LIKE 'Innodb_rows_modified';结合Uptime算平均每秒修改行数,若该值低但History list length高,说明是“只改不提” -
created_tmp_disk_tables持续上涨?说明事务内反复建大临时表又不结束事务,每张磁盘临时表都占内存+磁盘,且无法被清理
别信 autocommit=1 就安全
很多团队以为设了 autocommit=1 就不用管事务,但 MySQL 在执行 DML 时仍会隐式开启事务(尤其当 session autocommit=1 但 global=0,或 JDBC URL 里写了 useSSL=false&autoReconnect=true 却漏了 defaultAutoCommit=true)。
- 连上库后立刻执行
SELECT @@autocommit, @@global.autocommit;,两个都得是 1 才真正自动提交 - Spring Boot 默认
spring.datasource.hikari.auto-commit=true,但如果自定义了HikariConfig并手动 setAutoCommit(false),那就全靠代码兜底 - 用
tcpdump或 MySQL 通用日志(general_log=ON)抓包验证:有没有BEGIN发出去却没跟COMMIT或ROLLBACK
真正难排查的从来不是“谁没关连接”,而是“谁开了事务却假装没开”——它不报错、不涨连接数、不进慢日志,只悄悄吃掉内存和 undo 空间。盯紧 INNODB_TRX.TRX_STARTED 和 PROCESSLIST.TIME 的时间差,比看任何监控曲线都准。


















