未提交事务导致连接池变空,因mysql持续持有连接不归还,连接池无法回收;表现为threads_connected攀升、新请求超时。

事务开启后没提交,连接就一直被绑在那个线程上不放——不是数据库拒绝服务,是连接“借出去就再没还”。
为什么未提交事务会让连接池越来越空
MySQL 在事务未提交(COMMIT)或回滚(ROLLBACK)前,会持续持有该连接,状态通常为 Query 或 Locked,不会进入 Sleep。这意味着:
- 连接池无法回收它,哪怕应用逻辑早已执行完
-
wait_timeout对它无效——超时机制只作用于Sleep状态连接 - 每个长事务都计入
Threads_connected,堆积几十个就可能打满max_connections - 新请求只能排队等连接,最终触发
Connection is not available, request timed out
常见漏掉提交的场景
不是所有“没提交”都源于手抖忘写 COMMIT,更多是隐式陷阱:
- Java 中
@Transactional注解失效(比如内部调用、非 public 方法),导致事务没开启,但代码又手动conn.setAutoCommit(false)了 - Python 使用
autocommit=False的connection,但异常分支里漏了rollback(),或return前没commit() - 存储过程里有多个
SELECT,JDBC 没调用getMoreResults()消费全部结果集,连接卡在等待状态 - 事务内调用了 HTTP/RPC,下游响应慢或失败,整个事务就 hang 在那里,连带连接一起挂起
怎么快速确认是不是事务卡住的锅
别先改配置,先登录 MySQL 查真实占用者:
- 查运行超 60 秒的活跃事务:
SELECT trx_id, trx_state, TIMEDIFF(NOW(), trx_started) AS duration, trx_query FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60; - 重点看
trx_state = 'RUNNING'且duration持续增长的记录 - 用
TRX_MYSQL_THREAD_ID关联PROCESSLIST,确认来源 IP 和命令:SELECT ID, USER, HOST, COMMAND, TIME, INFO FROM information_schema.PROCESSLIST WHERE ID = ?; - 如果
COMMAND = 'Sleep'但TIME很大,大概率是应用开了事务却提前退出,没清理
真正管用的防御手段
靠人盯不现实,得靠机制兜底:
- 应用层必须设事务超时:HikariCP 的
transaction-timeout建议设为 10 秒,明显小于 DB 的wait_timeout - DB 层收紧锁等待:
SET SESSION innodb_lock_wait_timeout = 15;,避免卡在行锁上不动 - 批量操作必须分段提交:10 万条数据别包进一个事务,每 500 条
COMMIT一次 - 禁用全局
autocommit=0:查SELECT @@global.autocommit;,如果是 0,立刻改掉——这是静默泄漏温床
最危险的不是慢 SQL,而是那些 trx_query 为空、trx_state 为 RUNNING、trx_started 却是两小时前的事务——它不报错,不慢,只是安静地把连接池抽干。











