发布时间:2026-09-11 04:36 更新时间:2026-09-11 04:36 阅读量:0
站点突然打不开,PHP 或 Java 应用日志里刷出 ERROR 1040 (HY000): Too many connections,这是很多站长和运维都会撞上的场景。它通常不代表数据库真的扛不住了,而是连接数上限被一批空闲或被泄漏的连接占满,新请求连握手的机会都没有。下面按“先看清是谁占的、再调池子和超时、最后才动上限”的顺序,把可执行的做法讲一遍。
MySQL 能接受的并发连接总数由 max_connections 控制,历史上默认值是 151,但不同版本和发行版的打包配置可能覆盖过这个值,具体以 SHOW VARIABLES 的返回为准。真正需要盯的是两个状态量:Threads_connected 是当前已建立的连接数,Threads_running 是其中正在执行语句的线程数。如果前者已经贴着上限,后者却只有个位数,基本可以断定是空闲连接堆积,而不是并发压力。
mysql -uroot -p -e "SHOW STATUS LIKE 'Threads_connected'; SHOW STATUS LIKE 'Threads_running'; SHOW VARIABLES LIKE 'max_connections';"
接下来看这些连接具体来自哪。用 information_schema.processlist 比直接敲 SHOW PROCESSLIST 更好用,因为它支持排序和过滤,字段含义与前者一致。time 列对 Sleep 状态的连接来说就是空闲秒数,这一列最能说明问题。
SELECT id, user, host, db, command, time, state, LEFT(info, 80) AS info
FROM information_schema.processlist
WHERE command <> 'Sleep'
ORDER BY time DESC
LIMIT 20;
如果怀疑是某个来源把连接吃光了,按来源地址聚合统计一眼就能看出:
SELECT host, user, COUNT(*) AS conns
FROM information_schema.processlist
GROUP BY host, user
ORDER BY conns DESC
LIMIT 15;
读结果时注意几种典型形态。第一,某个 user 或某个内网 IP 占了绝大多数,多半是应用侧连接池开得过大,或者某段代码开了事务没提交、拿了连接没归还。第二,大量 Command=Sleep 且 time 是几万秒,说明服务端的空闲超时太长,连接长期挂着不走。第三,看到 Waiting for table metadata lock 这类 state,说明问题其实在锁上,连接是被阻塞而不是被占用,治法和调连接数完全不同。
确认了要清理的连接后,可以用 KILL 断开。这里有个容易踩的坑:优先用 KILL QUERY 只终止正在跑的语句,实在清不掉再 KILL CONNECTION。另外,查看别人的连接需要 PROCESS 权限,终止别人的连接需要 CONNECTION_ADMIN 权限(老版本是 SUPER),权限不足时操作会直接报错,别以为是命令写错了。
KILL QUERY 12345;
KILL CONNECTION 12345;
很多“连接数打满”的根因不在数据库,而在应用侧连接池。两个数字要一起算:所有应用实例数 × 单实例池的最大连接数,这个乘积必须明显小于 max_connections,并且给备份脚本、监控采集、人工登录留出余量。10 个实例每个池子开 20,就是 200 条连接,再配上默认的 151,注定要报 1040。
第二个关键点是生命周期对齐。maxLifetime(连接池里连接的存活上限)要小于服务端的 wait_timeout,否则服务端会先把空闲连接掐断,池子却还认为它是好的,下一次取出来用就报连接已断开。MySQL 的 wait_timeout 默认是 28800 秒,即 8 小时,HikariCP 的 maxLifetime 默认是 1800000 毫秒(30 分钟),这个组合本身是安全的;但如果为了回收空闲连接把 wait_timeout 调到了几分钟,就必须同步下调池子的 maxLifetime。
[mysqld]
max_connections = 500
wait_timeout = 600
interactive_timeout = 600
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
idle-timeout: 300000
max-lifetime: 540000
connection-timeout: 3000
上面这组值只是示例,思路是让 max-lifetime(540 秒)比 wait_timeout(600 秒)短一点,留出安全余量;具体数值要按业务 QPS、单条 SQL 耗时和实例数量自己算,直接抄参数往往不合适。另外 wait_timeout 也别调得过小,频繁建连断连本身也有开销,还可能让 TLS 握手和权限校验的 CPU 消耗上升。
临时抬升上限可以直接在会话里执行,但它重启即失效,只适合抢修:
mysql -uroot -p -e "SET GLOBAL max_connections = 800;"
要长期生效得写进配置文件(通常是 /etc/my.cnf 或 /etc/mysql/mysql.conf.d/mysqld.cnf,路径以实际环境为准),改完重启服务。这里必须提醒一句:调大 max_connections 不等于没有代价。每条连接都会占用线程栈以及连接级的缓冲区,像 sort_buffer_size、join_buffer_size、read_buffer_size 这些参数是按连接分配的,如果它们被设得很大,把连接数从 151 抬到上千,内存可能先撑不住,触发 OOM 反而更糟。所以这些 per-connection 缓冲保持默认或较小值,是连接数能安全上调的前提。
还有一个常被忽略的参数是 back_log,它决定短时间内允许有多少个连接请求排队等待,默认值跟操作系统有关。并发建连的瞬时峰值很高时,可以适度调大;但这个值受操作系统 listen 队列长度限制,不宜盲目设置。
归纳一下抢修顺序:先看 Threads_connected 与 Threads_running 判断是空闲堆积还是真忙;用 information_schema.processlist 定位来源;清理掉确定无用的空闲连接;临时上调上限争取时间;最后回到连接池配置和代码里的事务、连接释放上做修复。日常运维建议把 Threads_connected / max_connections 的比值做成监控指标,比如超过 70% 就告警,这样能在用户报障之前就发现问题。配置改动前请先在测试环境验证,具体参数含义以对应版本的官方文档为准。
| 📑 | 📅 |
|---|---|
| Linux用户与权限管理实战:sudoers、SUID/SGID与最小权限 | 2026-09-10 |
| systemd服务管理实战:从Unit编写到开机自启与自动重启 | 2026-09-10 |
| 网站服务器迁移完整流程:数据同步、解析切换与验证 | 2026-09-09 |
| 服务器日志分析:grep、awk 与 GoAccess 快速排障指南 | 2026-09-09 |
| Nginx反向代理配置详解:多站点代理与负载均衡实战 | 2026-09-09 |
| Nginx 502/504 排查实战:从 upstream 超时到 php-fpm 进程池 | 2026-09-12 |
| Docker 磁盘占用过高清理实战:overlay2、容器日志与悬空镜像 | 2026-09-12 |
| Let's Encrypt 证书自动续期失败排查:日志、80端口校验与 reload 钩子 | 2026-09-13 |
| Nginx 日志按天切割与过期清理:logrotate 实战 | 2026-09-15 |
| Docker Compose 部署多容器应用实战:环境变量、数据卷与重启策略配置要点 | 2026-09-15 |