一聚教程网:一个值得你收藏的教程网站

热门教程

MySQL连接数打满(Toomanyconnections)排查与治理实战

时间:2026-08-13 10:19:51 编辑:袖梨 来源:一聚教程网

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

MySQL连接数打满(Toomanyconnections)排查与治理实战

生产环境最常见的两个信号:应用日志批量刷 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');"

关键看三个数:

  1. Threads_connected:当前活着的总连接数,是否等于或逼近 max_connections
  2. Threads_running:真正在执行的线程,通常远小于 connected,如果它也接近上限,说明不是空闲连接堆积,而是真有海量并发 SQL
  3. Max_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 里重点盯两类:

  1. command='Sleep'time 很大的:这是已经空闲但没被客户端释放的连接,往往占掉大半配额
  2. state 卡在 Sending data / Locked / Waiting for table flushtime 长:慢查询或锁等待占着连接不撒手

四、四类根因,按出现频率排序

  1. 连接池没设上限或设太大。应用侧连接池 max 配得比数据库 max_connections 还大,N 个实例一扩,总连接直接翻倍超 DB 上限。
  2. wait_timeout / interactive_timeout 过长。默认 28800 秒(8 小时),客户端异常退出时 DB 侧连接要等 8 小时才回收,空闲连接长期占坑。
  3. 慢查询 / 锁等待。一条全表扫或行锁未提交的事务,把连接钉住几十秒,并发一来连接池迅速被耗光。
  4. 应用拿了连接不关。代码里 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';"
参数默认中高并发建议说明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_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=1long_query_time=1)定期巡检,基本能把这类问题挡在故障之前。

热门栏目