长事务未提交导致连接池耗尽,本质是连接“借出未还”,需查活跃事务、锁等待及进程列表,修复编程式事务漏洞并配置多层超时与连接池监控。

长事务未提交导致连接池被静默耗尽,本质是连接“借出去没还”,池子越用越空,新请求卡在排队队列里,直到超时报错——Connection is not available, request timed out。这不是数据库挂了,而是连接被事务悄悄锁死、没人释放。
查清谁在占着连接不放
先别急着调大连接数。登录数据库执行:
-
MySQL:
SELECT trx_id, trx_started, TIMEDIFF(NOW(), trx_started) AS duration, trx_state, trx_query FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60;(查超60秒的活跃事务) - 同时看有没有锁等待:
SELECT * FROM sys.innodb_lock_waits; - 再确认应用侧当前活跃连接:
SELECT * FROM information_schema.PROCESSLIST WHERE COMMAND != 'Sleep';
重点盯住 trx_state = 'RUNNING' 且 duration 持续增长 的记录,它大概率就是那个“静默占用者”。
堵住编程式事务的“提前返回”漏洞
注解式事务(@Transactional)由Spring自动管理提交/回滚;但一旦切到编程式事务,就极易因忘记提交、异常后没回滚、或中途 return,导致连接一直绑定在线程上不释放。
- 错误写法:开完事务直接 return,status 对象没任何处理
- 正确写法:必须用 try-catch-finally 确保终态
示例修复:
TransactionStatus status = null;
try {
status = transactionManager.getTransaction(def);
mapper.update(...);
transactionManager.commit(status); // 显式提交
} catch (Exception e) {
if (status != null && !status.isCompleted()) {
transactionManager.rollback(status); // 显式回滚
}
throw e;
} finally {
// 不依赖 commit/rollback 是否执行成功,只确保状态终结
if (status != null && !status.isCompleted()) {
transactionManager.rollback(status);
}
}给事务加“安全阀”:多层超时兜底
单靠代码自觉不可靠,必须配置硬性超时:
-
应用层:Spring
@Transactional(timeout = 30)(单位秒),超时自动回滚 -
JDBC 层:在 connection URL 加
&socketTimeout=30000,防网络卡死 -
数据库层:MySQL 设置
idle_in_transaction_session_timeout = 30000(毫秒),服务端强制杀掉空闲事务
三层叠加,避免某一层失效导致全局阻塞。
连接池本身也要“带监控上岗”
HikariCP 或 Druid 不能只配参数,得让它会“喊疼”:
- 开启泄漏检测:
leakDetectionThreshold=60000(60秒未归还就记日志) - 暴露监控端点:Druid 配
/druid/*,HikariCP 接入 Micrometer + Prometheus - 关键指标告警:当
activeCount ≥ maximumPoolSize × 0.8或notEmptyWaitCount > 0 持续1分钟,立刻触发告警
有数据,才能快速区分是突发流量、慢SQL、还是真·连接泄漏。

















