一、现象:不是慢,是直接连不进生产环境最常见的两个信号:应用日志批量刷 Too many connections,监控里 Threads_connected 顶到 max_connections 上限;与此同时新请求开始超时,老请求还在跑,但新连接一律被拒。这个状态本身不复杂,难在"为什么突然打满"
MySQL连接数打满排查与治理实战指南
时间:2026-08-14
浏览:73
一、现象:不是慢,是直接连不进

生产环境最常见的两个信号:应用日志批量刷 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_engine
engine = 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,这类问题大多都能在真正演变成故障前就被拦下来。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。