核心问题是定时任务线程复用导致数据库连接未归还,连接池耗尽;需通过show processlist查sleep连接、监控连接池泄漏、显式关闭连接(try-with-resources或finally)、启用leak-detection-threshold等措施定位并修复。

核心问题在于:定时任务长期复用线程(如 Spring @Scheduled 默认走固定线程池),每次执行时从连接池借出连接,但未主动归还,导致连接被线程长期持有,连接池逐渐枯竭,数据库端堆积大量 SLEEP 状态连接,最终触发 Too many connections 或请求阻塞。
确认是否为连接未释放导致的句柄慢性耗尽
先快速验证根因,避免误判:
- 查 MySQL 当前连接状态:
SHOW PROCESSLIST;,重点关注State = 'Sleep'且Time持续增长(如 >300 秒)的连接,数量随定时任务运行时间明显上升; - 统计闲置连接:
SELECT COUNT(*) FROM information_schema.processlist WHERE state = 'Sleep' AND time > 300;,若该值接近或超过max_connections的 70%,高度可疑; - 检查应用侧连接池监控(如 HikariCP 的
activevsidle数,Druid 的「泄漏连接堆栈」),若存在「已借出未归还」记录,即可锁定代码层泄漏。
立即止血:回收已有闲置连接
不重启服务也能缓解压力:
- 手动 Kill 长时间 Sleep 连接:
KILL [ID];(ID 来自SHOW PROCESSLIST),适合少量连接; - 批量清理超时 Sleep 连接(谨慎执行):
SELECT CONCAT('KILL ', id, ';') FROM information_schema.processlist WHERE state = 'Sleep' AND time > 600;,复制结果执行; - 临时调高 MySQL 全局上限(仅应急):
SET GLOBAL max_connections = 1000;,但需同步排查泄漏,否则只是拖延崩溃时间。
代码层修复:确保每次获取必归还
不能依赖连接池自动回收——线程不销毁,连接就不释放。必须显式管理生命周期:
- Java 推荐用
try-with-resources(自动 close):
try (Connection conn = dataSource.getConnection()) { /* 执行 SQL */ }; - 若无法用 try-with-resources,务必在
finally块中关闭:
finally { if (conn != null && !conn.isClosed()) conn.close(); }; - Spring 环境下,避免在 @Scheduled 方法内直接 new Connection,优先使用
JdbcTemplate或@Transactional,它们由框架保障资源释放; - 检查是否有「连接跨方法传递」「连接被缓存到 ThreadLocal」等反模式,这类写法极易绕过自动管理。
配置加固:堵住泄漏通道
双保险,降低人为失误影响:
- 连接池启用泄漏检测(如 HikariCP):
leak-detection-threshold=60000(单位毫秒),超时未归还会打印堆栈,直指泄漏点; - 设置合理空闲超时:
idle-timeout=600000(10 分钟),让长期闲置连接自动回收; - MySQL 端缩短等待断开时间:
SET GLOBAL wait_timeout = 300;(5 分钟),迫使客户端主动重连,避免僵尸连接堆积; - 限制单用户连接数(可选):
CREATE USER 'app'@'%' WITH MAX_CONNECTIONS_PER_HOUR 1000;,防止单应用失控拖垮全局。











