答案是先确认连接池是否泄漏,再决定是否调整max_connections;若threads_connected接近上限而threads_running极低,表明大量sleep连接未释放,根源多为应用未关闭连接或连接池idletimeout>wait_timeout。

先确认是不是连接池泄露,别一上来就调max_connections
看到 Too many connections 报错,第一反应不是改 max_connections,而是看 Threads_connected 和 Threads_running 的比值。如果前者接近上限(比如 498/500),后者却只有个位数(比如 3 或 5),那基本就是连接池泄露——大量连接卡在 Sleep 状态没归还,不是业务真忙,是“借了不还”。Max_used_connections 值长期 >90% max_connections 也是强信号。
用SHOW PROCESSLIST定位泄漏源头的IP和账号
执行 SHOW PROCESSLIST; 时重点盯三类连接:
-
State = 'Sleep'且Time > 300:空闲超 5 分钟,大概率是应用没关连接或连接池idleTimeout配得比 MySQL 的wait_timeout还长 -
User和Host高度集中(比如全是app@10.20.30.40):说明泄漏来自某个应用实例,不是全局配置问题 - 出现大量
unauthenticated user:不是代码问题,是 DNS 解析卡住或扫描流量,需查防火墙或开skip-name-resolve
更高效的方式是聚合统计:SELECT USER, HOST, COUNT(*) FROM information_schema.PROCESSLIST GROUP BY USER, HOST;,一眼看出哪个组合占了 300+ 连接。
HikariCP/Druid 这些连接池怎么配才不漏
连接池配置不当是泄漏主因,常见错误有:
-
maximumPoolSize(HikariCP)或maxActive(Druid)设得比 MySQL 的max_connections还高,多个服务一压就爆 - 连接池
idleTimeout(如 HikariCP 默认 10 分钟) > MySQL 的wait_timeout(默认 8 小时),连接永远等不到被 MySQL 主动断开,也归还不回池里 - 没开泄漏检测:
leak-detection-threshold设成 60000(毫秒),能抓到超过 60 秒没 close 的连接,日志里直接打堆栈 - PHP 脚本发生
fatal error时,mysqlnd不会自动 close,必须显式调mysqli_close()
代码里最容易漏掉close()的几个地方
连接泄露往往藏在异常路径里,尤其注意这些位置:
- try-catch 后面没写
finally { conn.close(); },或者用了 try-with-resources 但资源类型没实现AutoCloseable - Spring 事务方法里手动
DataSource.getConnection(),但没用@Transactional管理生命周期,事务结束也不释放 - MyBatis 的
SqlSession没调close(),尤其在非 Spring 环境下手动管理时 - 异步任务(如 CompletableFuture、线程池)里开了连接,但主线程已结束,子线程里的 close() 没被执行
最麻烦的是那种“看起来关了,其实没关”的情况:比如你调了 conn.close(),但底层连接池的 close() 实际只是归还,而你的代码又在归还后继续用这个 conn 对象做操作——这时候不会报错,但连接已经失效,下次再从池里取可能拿到脏连接,间接导致更多重试建连。











