线上业务突然大面积报错 ERROR 1040 (HY000): Too many connections,新请求全被拒绝——这是 MySQL 连接数满 最典型的症状。本文从连接数模型讲起,给出临时止血、根因定位到连接池与 wait_timeout 根治的可落地实战命令,帮你把故障从”重启就好”变成”不再复发”。
一、现象与报错特征
连接数满时,应用侧通常表现为:新建连接直接抛 Too many connections;已建立连接还能勉强工作,但延迟陡增;监控上 Threads_connected 曲线顶到 max_connections 上限。它和”慢查询””死锁”不同,前者是”处理得慢”,而连接数满是”连进来都来不及”。
| 现象 | 说明 |
|---|---|
| 应用报 1040 错误 | 超过 max_connections,MySQL 拒绝新连接 |
| 监控 threads_connected ≈ max_connections | 连接池已耗尽,逼近上限 |
| DB 本身 CPU 不高 | 常见于”连接泄漏/池过大”,而非算力不足 |
| 重启后很快复发 | 根因未解,流量一上来又打满 |
二、MySQL 连接数模型:先看懂这几个关键量
要治理连接数,必须分清”上限””当前””活跃”三件事。下面这组命令是排查的起点:
-- 允许的最大连接数(硬上限)
SHOW VARIABLES LIKE 'max_connections';
-- 当前已建立连接数
SHOW STATUS LIKE 'Threads_connected';
-- 正在执行查询的连接数(真正在干活)
SHOW STATUS LIKE 'Threads_running';
-- 非交互/交互连接的空闲回收时间
SHOW VARIABLES LIKE 'wait_timeout';
SHOW VARIABLES LIKE 'interactive_timeout';
简单说:Threads_connected 不能超过 max_connections;而 Threads_running 才是真实并发压力。如果 connected 很高、running 很低,说明大量连接是”闲着没释放”,问题在应用端而非数据库。
| 变量/状态 | 含义 | 调优方向 |
|---|---|---|
| max_connections | 最大并发连接上限 | 临时止血可上调,但受内存限制 |
| Threads_connected | 当前连接总数 | 逼近上限即告警 |
| Threads_running | 活跃执行连接 | 高则需优化慢查询 |
| wait_timeout | 空闲连接回收秒数 | 过长会堆积空闲连接 |
三、快速止血:临时调大 max_connections
故障期间先保业务,用 SET GLOBAL 临时放大上限(重启后失效,仅应急):
-- 临时把上限提到 500(按内存与业务估算)
SET GLOBAL max_connections = 500;
-- 改完立即确认生效
SHOW VARIABLES LIKE 'max_connections';
注意:每个连接都会占用 sort_buffer_size、read_buffer_size 等线程级内存,盲目调大会把实例撑到 OOM。若 MySQL 跑在 Docker 容器 中,容器的内存上限也会限制你能开多大,调之前先看 docker stats 与宿主机剩余内存。
四、根因定位:连接为什么降不下来
4.1 应用未释放连接(连接泄漏)
代码里拿到连接后没在 finally/try-with-resources 中关闭,连接一直挂到超时。这是最常见的”重启就好、过会儿又满”的真凶。
4.2 连接池配置过大
如 HikariCP 的 maximumPoolSize 设成 200,10 个服务实例就有 2000 个连接压向单库。池不是越大越好,应等于”单实例并发 × 实例数”的保守估计。
4.3 慢查询 / 长事务占满
一条没走索引的 SQL(详见 MySQL 索引底层与最左前缀)可能在 Threads_running 上挂很久,连接长期不释放,最终连带打满。
4.4 wait_timeout 过长
默认 28800 秒(8 小时)意味着一个忘了关的连接会占用半天。对 Web 应用,通常设 60~300 秒足够。
五、实战排查清单(含聚合命令)
用 information_schema.processlist 按来源 IP、用户、状态聚合,一眼看出是谁在”霸占”连接:
-- 按客户端 IP 聚合连接数,找"刷屏"的来源
SELECT SUBSTRING_INDEX(host,':',1) AS client_ip, COUNT(*) AS cnt
FROM information_schema.processlist
GROUP BY client_ip ORDER BY cnt DESC;
-- 按用户聚合
SELECT user, COUNT(*) AS cnt
FROM information_schema.processlist
GROUP BY user ORDER BY cnt DESC;
-- 按状态聚合,定位是 Sleep 堆积还是 Query 卡住
SELECT state, COUNT(*) AS cnt
FROM information_schema.processlist
GROUP BY state ORDER BY cnt DESC;
-- 揪出执行时间最长、可能拖垮连接的会话
SELECT id, user, host, db, time, state, info
FROM information_schema.processlist
WHERE command != 'Sleep' ORDER BY time DESC LIMIT 20;
若 Sleep 状态占了绝大多数,基本锁定”连接泄漏或 wait_timeout 过长”;若 Query 状态且 time 很大,则是慢 SQL/长事务。
| processlist 状态 | 指向的根因 |
|---|---|
| 大量 Sleep 且 time 很大 | 连接泄漏 / wait_timeout 过长 |
| 大量 Query 且 time 很大 | 慢查询 / 长事务 |
| 同 IP 连接数异常高 | 某服务实例连接池过大 |
| 单一 user 占满 | 该应用未正确释放连接 |
六、根治方案:从”调大”到”治本”
-- 把空闲回收时间收到合理范围(需写进配置文件持久化)
SET GLOBAL wait_timeout = 300;
SET GLOBAL interactive_timeout = 300;
-- 持久化示例(my.cnf / my.ini)
-- [mysqld]
-- max_connections = 500
-- wait_timeout = 300
-- interactive_timeout = 300
应用端则要:① 用连接池并配置 leakDetectionThreshold 主动发现泄漏;② 把 maximumPoolSize 调到与真实并发匹配;③ 给 SQL 加超时与熔断。架构上,对读多写少的场景可引入 合理的数据库设计规范 与读写分离,把连接压力分摊;上线前用 sysbench 压测 验证连接上限是否够用。
| 方案 | 适用场景 | 成本 |
|---|---|---|
| 调小 wait_timeout | 空闲连接堆积 | 低,立即可做 |
| 修复连接泄漏 | 代码未关闭连接 | 中,需改代码 |
| 收敛连接池大小 | 多实例池过大 | 低,配置调整 |
| ProxySQL 限流/复用 | 微服务多客户端 | 高,需引入中间件 |
| 读写分离 | 读多写少 | 中,架构改造 |
七、落地 Checklist(避坑清单)
| 步骤 | 动作 |
|---|---|
| 1. 止血 | SET GLOBAL max_connections 临时上调,恢复写入 |
| 2. 看数 | 对比 Threads_connected / running,判断是泄漏还是慢查询 |
| 3. 聚合 | 按 IP/user/state 聚合 processlist,定位来源 |
| 4. 治本 | 修泄漏 + 收池 + 降 wait_timeout + 加限流 |
| 5. 验证 | 压测连接上限,监控 threads_connected 趋势 |
八、小结
MySQL 连接数满的本质,是”连接的产生速度超过了释放速度“。临时调大 max_connections 只是止血,真正的根治要从连接泄漏、连接池大小、wait_timeout 和架构分流四方面同时下手。把上面的 processlist 聚合命令存进监控面板的定时巡检,就能在打满之前提前预警。更多 MySQL 深度调优可参考 MySQL 深度调优实战。
九、容易被忽略的两个参数与监控预警
除了 max_connections,还有两个参数常在故障复盘时才被想起。其一是 max_user_connections,它按”用户”单独限连接数——当某个应用账号的连接把全局上限吃掉前,可能先触发该账号自己的上限,排查时要一起看;其二是 max_connect_errors,若某 IP 频繁握手失败(如错误的密码、网络抖动)超过阈值,MySQL 会直接拉黑该 host,报 Host 'x.x.x.x' is blocked,此时不是连接数满,却表现得像连不上,用 FLUSH HOSTS 即可解封。
-- 查看每个用户的上限(0 表示不限制,沿用全局)
SHOW VARIABLES LIKE 'max_user_connections';
SHOW VARIABLES LIKE 'max_connect_errors';
-- 某 host 被拉黑时解封
FLUSH HOSTS;
-- 观察线程缓存命中,判断是否频繁创建销毁连接线程
SHOW STATUS LIKE 'Threads_created';
SHOW STATUS LIKE 'Connections';
从架构层面,MySQL 是”一个连接对应一个线程”的模型,海量短连接会带来频繁的线程创建/销毁开销;引入线程池插件或前置连接复用层(如 ProxySQL)能显著降低这种抖动。最后别忘了把”连接数水位”做成监控:当 Threads_connected / max_connections 超过 70% 就告警,并配合应用侧限流(如网关令牌桶),把”事后救火”变成”事前拦截”。




