答案是:先查Threads_connected与Threads_running是否严重失衡,再确认wait_timeout和连接池配置是否匹配。出现“Too many connections”时90%是应用未释放连接或连接池配错,而非max_connections过小;Threads_connected接近上限但Threads_running极低,表明大量Sleep连接堆积;调大max_connections需同步解决系统文件描述符、内存开销及连接池idleTimeout ≤ wait_timeout等三重限制。

连接数打满不是配置太小,而是连接被卡住没释放——直接调大 max_connections 只会让问题延迟爆发,甚至压垮内存。
怎么看当前连接到底卡在哪?
别急着改配置,先登录 MySQL(用 socket 直连:mysql -u root -p -S /var/lib/mysql/mysql.sock),执行这三条命令:
-
SHOW GLOBAL STATUS LIKE 'Threads_connected';—— 看当前总连接数 -
SHOW GLOBAL STATUS LIKE 'Threads_running';—— 看真正正在干活的线程数 -
SELECT * FROM information_schema.PROCESSLIST WHERE COMMAND != 'Sleep' ORDER BY TIME DESC LIMIT 20;—— 找出非空闲、耗时长的连接
如果 Threads_connected 接近 max_connections,但 Threads_running 很低,说明大量连接停在 Sleep 状态;再看 PROCESSLIST 里这些 Sleep 连接的 Time 值——超过 300 秒的,基本就是泄漏或未关闭的连接。
为什么连接不释放?常见代码坑点
应用层是连接泄漏高发区,尤其以下几种写法:
- Go 的
rows没调rows.Close(),哪怕defer rows.Close()写了,也得确保它真被执行(比如函数提前 return) - Java 里用
Connection或Statement后,没在finally块里显式close(),异常路径直接跳过释放逻辑 - 事务开启后,只在 success 分支
commit,失败分支既没rollback也没close,连接一直挂起 - ORM 中嵌套事务或手动
begin后忘了end,连接被事务上下文长期持有
这类问题低流量时不明显,一到并发上来,连接池里的连接就慢慢被吃光,新请求全卡在获取连接这一步。
SQL 卡住也会“吃掉”连接
连接没泄漏,但连接数还在涨?大概率是 SQL 被锁或执行太久。重点查:
-
PROCESSLIST里State是Locked、Waiting for table metadata lock或Sending data且Time很高的连接 - 慢查询日志中执行时间 > 1s 的语句,特别是没走索引的
UPDATE或DELETE,它们会持锁+占连接 - 事务里混了 HTTP 调用、文件读写、循环处理等耗时操作,导致事务迟迟不提交
一个没加索引的 UPDATE 锁住上万行,后面所有想更新同一张表的请求都会排队等锁,每个都在占用一个连接——这时 CPU 和 IO 可能很低,但连接数稳步上涨。
临时救火和长期配置怎么设才靠谱?
应急可以快速止血,但必须配合根因动作:
- 紧急 KILL 长时间阻塞的连接:
KILL <code>ID;(ID 来自PROCESSLIST),优先杀Time > 600且State异常的 - 临时调大
max_connections:仅限SET GLOBAL max_connections = 500;,重启后失效,不能替代修复 - 永久生效要改配置文件:
max_connections = 300(根据实际并发量设,别盲目堆到 1000+) - 必须配
wait_timeout = 300和interactive_timeout = 300,让空闲连接自动断开,防堆积
真正容易被忽略的是:wait_timeout 对应用连接池无效——它只作用于未启用连接池的直连客户端。如果你用的是 HikariCP、Druid 或 Go 的 sql.DB,得在连接池配置里单独设 connection-timeout 和 idle-timeout,否则数据库侧的超时根本不起作用。


















