朕喜技术(ZhenxiJS)
2026.09.02
1 分钟阅读
MySQL Too many connections(ERROR 1040)连接数耗尽:诊断、应急恢复与根治
日期:2026-09-02 技术栈:MySQL 8.0、Docker Compose、pymysql、连接池 关键词:Too many connections、ERROR 1040、HY000、max_connections、Threads_connected、 SHOW PROCESSLIST、连接泄漏、connection leak、连接池、mysql Connection refused、应用连接失败
一、问题背景
在线商城后端在高并发期突然大量报错:
pymysql.err.OperationalError: (1040, 'Too many connections')
ERROR 1040 (HY000): Too many connections
用 mysql -u appuser ... 或应用任何新连接都失败,业务全部瘫痪。这是生产环境高频故障:
MySQL 的 max_connections 被连接(尤其是空闲/泄漏连接)占满后,任何新连接都会被拒绝。
二、复现步骤
# 1. 启动 MySQL(max_connections=20)+ app 容器(python3+pymysql)
cd scenario/2026-09-02-1600
docker compose up -d mysql
# 等 healthy 后,在 compose 网络内启动 app 容器
docker run -d --name mysql-toomany-app --network 2026-09-02-1600_default \
-v /workspace:/workspace -w /workspace 3day-problem:fix sleep infinity
# 2. 模拟连接泄漏:创建 25 个连接不关闭(> max_connections=20)
docker exec -d mysql-toomany-app python3 -c "
import pymysql,time
conns=[]
for i in range(25):
try:
c=pymysql.connect(host='mysql',user='appuser',password='apppass',database='myapp',connect_timeout=5)
conns.append(c); print('[conn %d] OK'%(i+1),flush=True)
except Exception as e:
print('[conn %d] FAIL: %s'%(i+1,e),flush=True)
time.sleep(3600)"
# 3. 新连接被拒
docker exec mysql-toomany-app python3 -c "
import pymysql
try:
pymysql.connect(host='mysql',user='appuser',password='apppass',database='myapp',connect_timeout=5)
print('SUCCESS')
except pymysql.err.OperationalError as e:
print('FAILED:',e.args)" # (1040,'Too many connections')
三、根本原因
关键诊断命令:
SHOW VARIABLES LIKE 'max_connections'; -- 20(上限)
SHOW STATUS LIKE 'Threads_connected'; -- 21(已占满)
SHOW STATUS LIKE 'Max_used_connections'; -- 21(历史峰值)
SHOW PROCESSLIST; -- 20 个 appuser 全部 Sleep 空闲
根因 = 配置过大 + 连接泄漏:
- 连接池
pool_size=50远超max_connections=20,初始化即建满 20 条连接; - 代码
finally里pass,连接从不 close/归还 → 每个请求再泄漏一条; - 无重试兜底,一遇 1040 业务立即崩溃。
现象细节:普通
appuser连不上,但 root/SUPER 用户仍能连接(MySQL 为管理用户保留连接),这正是应急恢复的入口。
四、解决办法
4.1 应急恢复(治标)
# root 登录(连接打满也能连),杀掉所有 appuser 空闲连接
docker exec mysql-toomany-app python3 -c "
import pymysql
c=pymysql.connect(host='mysql',user='root',password='rootpass',database='myapp')
cur=c.cursor(); cur.execute('SHOW PROCESSLIST')
for r in cur.fetchall():
if r[1]=='appuser' and r[4]=='Sleep':
cur.execute('KILL '+str(r[0]))
c.close()"
也可动态调大/缩短超时:SET GLOBAL max_connections=200; SET GLOBAL wait_timeout=300; SET GLOBAL interactive_timeout=300;
4.2 根治
- 合理连接池:池上限应远小于 MySQL
max_connections(本例设 8),并真正实现 acquire/release 复用,不再每次新建; - finally 归还连接:杜绝
pass不关闭; - 重试 + 指数退避:对瞬时
1040/2006/2013自动重试。
五、验证
docker exec mysql-toomany-app python3 -c "
import pymysql
c=pymysql.connect(host='mysql',user='appuser',password='apppass',database='myapp')
cur=c.cursor(); cur.execute('SHOW STATUS LIKE \"Threads_connected\"')
print(cur.fetchall())" # 修复后稳定回到 1,30 次请求 100% 成功、无泄漏
| 验证项 | 修复前 | 修复后 |
|---|---|---|
| 30 次业务请求 | 大量 1040 失败 | 100% 成功 |
| Threads_connected | 打满 20+ | ≤ 池上限 8 |
| appuser 新建连接 | 1040 | 正常成功 |
六、经验与避坑
- 连接打满时 root 总能连——这是应急第一入口,root 密码务必管好;
- 用
SHOW PROCESSLIST看连接状态:Sleep=空闲泄漏,Query=活跃; - 只看
max_connections会漏掉活跃占用;配合Threads_connected、Max_used_connections一起看; - 单纯调大 max_connections 是治标不治本:代码泄漏会继续吃光连接,只是拖延打满时间;
- 连接池上限要「留余量」(远小于 max_connections),连接必须 try/finally 归还;
- 应用对 1040/断线要做重试退避,避免瞬时故障变雪崩;
- 排查时先确认是不是「同一来源占满」,可用
SHOW PROCESSLIST看 Host 字段定位是哪个应用在泄漏。
