根本原因是非交互式sleep连接长期未释放,占满max_connections;需用show full processlist或查询information_schema.processlist筛选command='sleep'且time>300的连接,结合wait_timeout、连接池配置及max-active与max_connections匹配度综合排查。

直接结论:不是连接数设得太小,而是大量非交互式连接(Sleep 状态)卡在那儿不释放,占满 max_connections。 这类连接通常来自应用未正确关闭、连接池配置失当或脚本执行后残留,SHOW PROCESSLIST 里一眼就能看到几十上百个 Sleep,Time 列动辄几百秒甚至上千秒。
查当前谁在“睡”——SHOW PROCESSLIST 要带条件过滤
默认 SHOW PROCESSLIST 只显示前 100 条,且不区分用户/状态,容易漏掉关键线索。实际排查必须加筛选:
-
SHOW FULL PROCESSLIST:避免 SQL 被截断,看清完整语句 - 重点关注
Command = 'Sleep'且Time > 300(5 分钟以上)的连接 - 用
WHERE过滤非交互式连接:SELECT * FROM information_schema.PROCESSLIST WHERE COMMAND = 'Sleep' AND TIME > 300; - 注意
User和Host列:同一应用多实例、同一 IP 多端口、脚本工具(如 Navicat、DBeaver)连着没关,都可能集中暴露
非交互式连接不释放的三大主因
这类连接不会自己消失,除非超时或被杀。常见根因有:
-
wait_timeout和interactive_timeout值过大(比如设成 28800 秒即 8 小时),导致空闲连接长期滞留 - 应用代码中
Connection.close()没进finally块,异常路径跳过释放 - 连接池配置了
remove-abandoned-on-borrow=true但remove-abandoned-timeout设得太大(如 600 秒),等太久才回收
特别注意:MySQL 对 Sleep 连接只按 wait_timeout(非交互)或 interactive_timeout(交互)判断超时,二者默认值不同,且应用连接默认走 wait_timeout 路径。
临时清理 + 长效预防怎么做
不能只靠 KILL 手动清,要双管齐下:
- 紧急清理:用
SELECT CONCAT('KILL ',ID,';') FROM information_schema.PROCESSLIST WHERE COMMAND = 'Sleep' AND TIME > 600;生成批量KILL语句,再执行 - 服务端调低超时:
SET GLOBAL wait_timeout = 300;(建议 300~600 秒),SET GLOBAL interactive_timeout = 300; - 连接池侧强制回收:以 Druid 为例,确认启用
remove-abandoned-on-borrow=true且remove-abandoned-timeout=60(秒),比wait_timeout小 10~30 秒更稳妥 - 检查应用日志是否频繁打印
log-abandoned-connections=true触发的警告,那是连接泄漏的明确信号
最容易被忽略的点:连接池 max-active 和数据库 max_connections 不匹配
一个微服务设 max-active=50,部署 8 个实例,理论最大占用 400 连接;若 MySQL max_connections=300,还没算其他服务、定时任务、DBA 工具,必然爆。更隐蔽的是:
-
max_connections是全局硬上限,所有用户(包括root)共享 -
max_user_connections可单独限制某用户(如应用专用账号),防止单一服务吃光全部连接 - 别只看
Threads_connected,盯紧Max_used_connections—— 如果它长期接近max_connections,说明不是突发流量,而是连接没归还











