最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
MySQL连接数打满(Toomanyconnections)排查与治理实战
时间:2026-08-13 10:19:51 编辑:袖梨 来源:一聚教程网
一、现象:不是慢,是直接连不进

生产环境最常见的两个信号:应用日志批量刷 Too many connections,监控里 Threads_connected 顶到 max_connections 上限;与此同时新请求开始超时,老请求还在跑,但新连接一律被拒。
这个状态本身不复杂,难在"为什么突然打满"——绝大多数不是真的流量暴涨,而是连接没被正常回收,雪球越滚越大。下面按"确认→定位→根因→治理"的顺序拆解。
二、先确认是不是真的打满了
不要只看报错,先读数确认当前水位:
# 当前最大连接数与已用连接数mysql -e "SHOW VARIABLES LIKE 'max_connections'; SHOW STATUS LIKE 'Threads_connected'; SHOW STATUS LIKE 'Threads_running';"
# 改成全局状态一次性看全
mysql -e "SHOW GLOBAL STATUS WHERE Variable_name IN ('Threads_connected','Threads_running','Max_used_connections','Aborted_connects','Connections');"
关键看三个数:
Threads_connected:当前活着的总连接数,是否等于或逼近max_connectionsThreads_running:真正在执行的线程,通常远小于 connected,如果它也接近上限,说明不是空闲连接堆积,而是真有海量并发 SQLMax_used_connections:历史峰值,对照max_connections看是否曾经顶满过
三、连接都耗在哪:按来源聚合
确认打满之后,下一步是看这些连接来自哪个应用、哪台机、卡在哪个库:
# 按客户端 IP 聚合连接数(在 mysql 里执行)mysql -e "SELECT SUBSTRING_INDEX(host,':',1) AS client, COUNT(*) AS conn FROM information_schema.processlist GROUP BY client ORDER BY conn DESC LIMIT 20;"
# 按 user 聚合
mysql -e "SELECT user, COUNT(*) AS conn FROM information_schema.processlist GROUP BY user ORDER BY conn DESC;"
# 看卡住的语句:执行时间超过 5 秒的连接
mysql -e "SELECT id, host, user, db, time, state, LEFT(info,80) AS sql_text FROM information_schema.processlist WHERE command<>'Sleep' AND time>5 ORDER BY time DESC;"
processlist 里重点盯两类:
command='Sleep'且time很大的:这是已经空闲但没被客户端释放的连接,往往占掉大半配额state卡在Sending data/Locked/Waiting for table flush且time长:慢查询或锁等待占着连接不撒手
四、四类根因,按出现频率排序
- 连接池没设上限或设太大。应用侧连接池
max配得比数据库max_connections还大,N 个实例一扩,总连接直接翻倍超 DB 上限。 wait_timeout/interactive_timeout过长。默认 28800 秒(8 小时),客户端异常退出时 DB 侧连接要等 8 小时才回收,空闲连接长期占坑。- 慢查询 / 锁等待。一条全表扫或行锁未提交的事务,把连接钉住几十秒,并发一来连接池迅速被耗光。
- 应用拿了连接不关。代码里 try 没 finally、异常分支漏了 close,连接泄漏,重启前只增不减。
五、治理:参数 + 连接池两头收
参数侧先看当前超时与线程缓存:
mysql -e "SHOW VARIABLES LIKE 'wait_timeout'; SHOW VARIABLES LIKE 'interactive_timeout'; SHOW VARIABLES LIKE 'thread_cache_size'; SHOW VARIABLES LIKE 'max_connections';"在线改(重启失效,用于应急):
mysql -e "SET GLOBAL wait_timeout=600; SET GLOBAL interactive_timeout=600; SET GLOBAL max_connections=800;"持久化写配置文件 /etc/my.cnf 的 [mysqld] 段:
[mysqld]max_connections = 800
wait_timeout = 600
interactive_timeout = 600
thread_cache_size = 32
应用侧连接池必须兜底,不能只靠 DB 调大。Python(SQLAlchemy)示例:
from sqlalchemy import create_engineengine = create_engine(
"mysql+pymysql://user:pass@db-host:3306/app",
pool_size=20, # 常驻连接上限
max_overflow=10, # 超出 pool_size 的临时连接
pool_recycle=280, # 小于 wait_timeout,提前回收防失效连接
pool_pre_ping=True, # 取出连接前探活,避免拿到已断开的连接
pool_timeout=30, # 拿不到连接等待上限,超时即抛错而非无限等
)
Go(database/sql)示例:
db, _ := sql.Open("mysql", "user:pass@tcp(db-host:3306)/app")db.SetMaxOpenConns(30) // 最大打开连接数
db.SetMaxIdleConns(10) // 最大空闲连接数
db.SetConnMaxLifetime(280 * time.Second) // 连接最长存活,小于 wait_timeout
db.SetConnMaxIdleTime(60 * time.Second) // 空闲回收时间
六、连接稳定与出口的关系
应用服务器到数据库、以及到下游第三方接口的长连接,如果底层经过会自动切换的共享线路,连接会随出口 IP 变化被对端判定为新建会话而频繁重建,无形中放大连接消耗。这类长连接常见的出口做法有几种:直接用云厂商分配的固定公网地址、走企业专线,或选按 IP 独立分配的服务商(如 LinkStatic 这类固定 ISP 专线)。无论哪种,核心都是让出口地址长期不变,配合上面的池化与超时治理,整体连接水位更可控。
七、长效机制:把阈值监控起来
治完要防止复发,最关键的是把连接数纳入监控并设告警:
# 每分钟采样,超过 max_connections 的 80% 就告警mysql -N -e "SHOW STATUS LIKE 'Threads_connected';" | awk '{c=$2} END{
m=system("mysql -N -e "SHOW VARIABLES LIKE '"'"'max_connections'"'"'" | awk "{print $2}"");
if (c > m*0.8) print "WARN threads_connected="c" max="m
}'
配合 Grafana 画 Threads_connected / max_connections 曲线,斜率异常上涨往往早于 Too many connections 报错,能争取到处理窗口。再叠加慢查询日志(slow_query_log=1、long_query_time=1)定期巡检,基本能把这类问题挡在故障之前。