先确认是否真连满:用root本地登录执行show variables like 'max_connections'、show status like 'threads_connected'和show status like 'threads_running',若前者接近上限而后者极低,说明大量sleep连接堆积,根源是应用未释放连接或连接池配置错误。

别急着改 max_connections,90% 的“Too many connections”不是上限太低,而是连接没释放——先确认是不是真连满了,再查谁在占着不放。
怎么快速确认是不是真连满了
哪怕应用全连不上,root 本地还能进(MySQL 预留了一个 SUPER 权限的紧急连接位):
- 执行
SHOW VARIABLES LIKE 'max_connections';—— 看上限,默认常是 151 - 执行
SHOW STATUS LIKE 'Threads_connected';—— 看当前连了多少个 - 执行
SHOW STATUS LIKE 'Threads_running';—— 看正在干活的线程数;如果Threads_connected接近上限但Threads_running很小(比如 2/500),说明几百个连接全卡在Sleep,基本就是泄漏或连接池 idle 超时比 MySQL 的wait_timeout还长
为什么 SET GLOBAL max_connections 经常不生效
这条命令看着快,实际卡在三道硬门槛上:
-
ulimit -n返回值低于设定值(如返回 1024,却设max_connections = 2000),MySQL 启动时会自动向下取整 - systemd 管理的服务必须在
/usr/lib/systemd/system/mysqld.service的[Service]段加LimitNOFILE=65536,然后执行systemctl --system daemon-reload && systemctl restart mysqld - MySQL 8.0.22+ 支持
SET PERSIST max_connections = 1000;,它会写入mysqld-auto.cnf,优先级高于my.cnf——若已用此方式,改my.cnf不覆盖
如何安全清理僵死连接而不是硬 kill
别一上来就 KILL 所有 Sleep 连接——有些是正常长连接(比如监控、ETL 工具):
- 查出空闲太久的连接:
SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE, INFO FROM information_schema.PROCESSLIST WHERE COMMAND = 'Sleep' AND TIME > 300;(>5 分钟才考虑) - 按用户/IP 归类看异常来源:
SELECT USER, HOST, COUNT(*) FROM information_schema.PROCESSLIST GROUP BY USER, HOST ORDER BY COUNT(*) DESC; - 对确认无用的连接,用
KILL <code>ID;逐个终止;不要用KILL QUERY,它只停语句,连接还占着 - 更稳妥的做法是调低超时:
SET GLOBAL wait_timeout = 120;(非交互式连接 2 分钟自动断),SET GLOBAL interactive_timeout = 180;(交互式连接 3 分钟)
连接池配置比 MySQL 参数更关键
多数线上事故根子在应用侧。比如 HikariCP 或 Druid,常见错配:
-
maximumPoolSize(或max-active)应 ≤max_connections的 70%,留余量给后台线程和管理员连接 - HikariCP 的
connection-timeout默认 30 秒,网络抖动易堆积;建议设为 10–15 秒,并开leak-detection-threshold - PHP 脚本若发生
fatal error,mysqlnd不会自动归还连接;必须显式mysqli_close()或依赖脚本结束释放(不可靠) - Druid 的
remove-abandoned-timeout必须小于 MySQL 的wait_timeout,否则连接池以为连接还活着,MySQL 却已断开,导致“连接已关闭”异常
真正难的不是临时扩容,而是把 Threads_connected 和 Max_used_connections 的差值稳定压在 10% 以内——这需要应用代码关连接、连接池配得准、MySQL 超时设得短,三者严丝合缝。











