一、现象:不是慢,是直接连不进

生产环境最常见的两个信号:应用日志批量刷 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 看是否曾经顶满过Threads_connected当前总连接数逼近 max_connectionsThreads_running正在执行的线程接近 connected,说明并发 SQL 真高Max_used_connections历史峰值多次触及上限,需调大或治本Aborted_connects失败连接累计持续增长,客户端配置或网络有问题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 小时才回收,空闲连接长期占坑。五、治理:参数 + 连接池两头收
参数侧先看当前超时与线程缓存:
mysql -e "SHOW VARIABLES LIKE 'wait_timeout'; SHOW VARIABLES LIKE 'interactive_timeout'; SHOW VARIABLES LIKE 'thread_cache_size'; SHOW VARIABLES LIKE 'max_connections';"max_connections151按实例数×池上限留余量不是越大越好,受内存限制wait_timeout28800300~600空闲非交互连接回收时间(秒)interactive_timeout28800300~600交互连接回收时间thread_cache_size8(或 0)如未读到默认则 16~32复用线程,降新建开销在线改(重启失效,用于应急):
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)定期巡检,基本能把这类问题挡在故障之前。
灵骏真武 M890 超节点实例上线!乌兰察布首发,64 卡 800GB/s 互联支撑万亿大模型
迅捷 FW3030R 无线路由器配置无线WiFi上网操作指南
面向连载内容的 AI 漫剧工程化实现:ComfyUI 工作流、帧校验与时序优化完整方案
12306候补订单一般多久能兑现-12306候补规则
使用 Netdata 给服务器增加一个实时监控面板
迅捷 FW150RM 无线路由器Router模式快速设置方法