MySQL连接数管理最佳实践:从Too many connections应急到长期治理

简介: 本文详解MySQL“Too many connections”故障的应急排查与根治方案:30秒快速评估、杀Sleep连接止血、动态调参救急;深入分析慢查询、连接泄漏、短连接冲击、配置过小四大根因;附诊断脚本、监控告警规则及连接池最佳实践,助你从容应对生产危机。

📌今日关键词:MySQL连接数、Too many connections、连接泄漏、max_connections、紧急排查


大家好,我是数据库小学妹 👋

上周五我正准备收拾东西下班,监控群突然炸了——"数据库连不上了!所有接口都在报错!"一看应用日志,满屏都是 Too many connections

说真的,那一瞬间心跳直接飙到120。

之前我们聊过连接池怎么设计才能预防这种问题,但预防归预防,真出了事怎么办?连接数爆满是生产环境里最常见的紧急故障之一,处理不好就是全线业务停摆。

今天把我的排查和处理过程整理出来,从"先活下来"到"搞清楚为什么",希望大家在遇到同类问题时,能少走弯路少踩坑,从容应对!


一、Too many connections 的本质

先搞清楚这个报错的本质。

MySQL 有个参数 max_connections,限制了同时能连多少个客户端。5.7和8.0默认是151个。当已建立的连接数达到这个上限,新的连接请求就会被拒绝,报 ERROR 1040 (HY000): Too many connections

注意一个细节:root用户在本地通过socket连接时,MySQL会预留一个额外的连接位。所以业务连不上了,DBA用root还是能登进去排查。这个设计就是给紧急情况留的后门。

连接数打满不等于数据库挂了。进程还在,数据也没丢,就是"门关了进不去"。搞清楚这一点,心态就稳了。


二、紧急评估:30秒内搞清楚状况

收到告警先别慌,三步快速判断局势。

第一步:MySQL 还活着吗

# 看进程
ps aux | mysqld | grep -v grep

# 看端口
ss -tlnp | grep 3306

# 看机器负载
uptime

进程在、端口在、负载没爆,说明MySQL本身没挂,就是连接满了。这算好消息。

第二步:连了多少,连的谁

# 用socket直连,不走TCP
mysql -u root -p -S /var/lib/mysql/mysql.sock -e "SHOW PROCESSLIST;" 2>/dev/null | wc -l

如果连socket都连不上(很少见),从操作系统层看:

# 看3306端口的连接总数
ss -ant | grep :3306 | wc -l

# 看每个IP的连接分布
ss -ant | grep :3306 | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn

第三步:区分两种关键指标

SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Threads_running';
SHOW VARIABLES LIKE 'max_connections';

Threads_connected 是已建立的连接总数,包含睡觉的。Threads_running 是正在跑SQL的连接数。

这两个数的区别很关键。如果 Threads_connected 高但 Threads_running 低,说明大量连接在Sleep,杀了就行。如果 Threads_running 也高,说明有东西在跑慢查询,得找到它干掉。


三、快速止血:先让业务活过来

确认了情况,开始止血。以下操作有侵入性,执行前心里要有数。

杀掉睡眠连接

大多数情况下,连接数爆满的元凶是大量Sleep连接。这些连接建立后啥也没干,或者干完了没关,白白占着坑位。

-- 杀掉超过30秒的睡眠连接
SELECT CONCAT('KILL ', ID, ';')
FROM INFORMATION_SCHEMA.PROCESSLIST
WHERE COMMAND = 'Sleep'
AND TIME > 30;

拿到结果后复制出来执行。或者更暴力一点:

# 一行搞定
mysql -u root -S /var/lib/mysql/mysql.sock -N -e "
SELECT CONCAT('KILL ', ID, ';')
FROM INFORMATION_SCHEMA.PROCESSLIST
WHERE COMMAND = 'Sleep' AND TIME > 30;
" | mysql -u root -S /var/lib/mysql/mysql.sock

我第一次处理这个问题的时候,把所有Sleep连接都杀了,包括一些正常的。后来才知道要加 TIME > 30 的条件,刚建立几秒的Sleep连接可能是正常的——应用从连接池取出来准备用,还没来得及发SQL。

临时提高 max_connections

清理完还不够?把上限临时拉高:

-- 动态调整,不用重启
SET GLOBAL max_connections = 500;

这个改法MySQL重启就失效了。想持久化的话用 SET PERSIST(8.0+)或者改my.cnf。但记住,这只是应急,不是解法。max_connections设太大,内存扛不住反而更惨。

杀掉慢查询

如果 Threads_running 很高,得找到那些跑得最久的查询:

SELECT
    ID,
    USER,
    HOST,
    DB,
    COMMAND,
    TIME,
    STATE,
    LEFT(INFO, 100) AS QUERY
FROM INFORMATION_SCHEMA.PROCESSLIST
WHERE COMMAND != 'Sleep'
ORDER BY TIME DESC
LIMIT 10;

看到执行了300秒的查询?大概率就是它在捣乱。确认不影响业务后干掉:

KILL <ID>;

注意区分:KILL 杀的是连接,KILL QUERY 只停查询不断连接。紧急情况下直接 KILL 更彻底。


四、根因分析:四个方向挨个查

血止住了,得搞清楚为什么会爆。连接数爆满不是病根,是症状。常见根因就四个。

根因一:慢查询堆积

这是最常见的原因。一条SQL执行30秒,这30秒内这个连接一直在干活,别的请求只能开新连接。如果慢查询持续涌入,连接数很快打满。

排查方法:

-- 通过performance_schema看哪些SQL最耗时
SELECT
    DIGEST,
    COUNT_STAR,
    SUM_TIMER_WAIT / 1000000000000 AS SUM_SECONDS,
    AVG_TIMER_WAIT / 1000000000000 AS AVG_SECONDS,
    LEFT(SQL_TEXT, 200) AS SQL_SAMPLE
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;

再看慢查询日志确认:

tail -100 /var/log/mysql/slow.log | grep -i "Query_time"

我遇到过一次,财务模块导出报表触发了一条跨五张表的JOIN查询,没走索引,单次执行3分钟。当天下午正好好几个财务在同时导出,连接直接打满。后来加了索引,执行时间从3分钟降到0.2秒,问题解决。

根因二:连接泄漏

这个最隐蔽。应用代码拿到连接后没关闭,连接一直占着不释放。少量泄漏看不出来,积累几天就爆了。

怎么判断是不是泄漏:

SELECT
    USER,
    HOST,
    COUNT(*) AS CONN_COUNT,
    MAX(TIME) AS MAX_TIME,
    MIN(TIME) AS MIN_TIME
FROM INFORMATION_SCHEMA.PROCESSLIST
WHERE COMMAND = 'Sleep'
GROUP BY USER, HOST
HAVING COUNT(*) > 10
ORDER BY CONN_COUNT DESC;

某个来源IP的Sleep连接数持续增长,而且 MAX_TIME 特别大(比如几千秒),基本就是泄漏了。

我之前见过的一个案例:Java应用的异常处理分支里,catch块只记了日志,没关连接。正常请求没问题,一碰到异常就漏一个。平时流量小看不出来,攒了一周,周五下午高峰时爆了。

修复代码很简单,用 try-with-resources

// 泄漏写法
public void queryData() {
   
    Connection conn = dataSource.getConnection();
    // 异常时conn不会被关闭
    query(conn);
}

// 正确写法
public void queryData() {
   
    try (Connection conn = dataSource.getConnection()) {
   
        query(conn);
    }
}
// 方法结束自动关闭,异常也会关

根因三:短连接冲击

PHP和部分Python应用默认用短连接——每次请求新建连接,用完就关。并发量一上来,MySQL要不停地做TCP握手、认证、分配线程,忙不过来连接就堆上了。

-- 看连接建立速率
SHOW STATUS LIKE 'Connections';
SHOW STATUS LIKE 'Aborted_connects';

Aborted_connects 持续涨,说明大量连接建立失败。结合 wait_timeout 的设置看:

SHOW VARIABLES LIKE 'wait_timeout';
SHOW VARIABLES LIKE 'interactive_timeout';

这两个值如果设得很短(比如60秒),Sleep连接60秒就断,应用下次请求又得新建。频繁建连关连,MySQL压力山大。

一般建议 wait_timeout = 600(10分钟),别太短也别太长。

根因四:max_connections 确实太小

MySQL默认151,中等流量的生产环境根本不够用。

SHOW VARIABLES LIKE 'max_connections';
SHOW STATUS LIKE 'Max_used_connections';

Max_used_connections 是MySQL启动以来的历史峰值。如果这个值已经很接近 max_connections,说明上限确实该调了。

但别拍脑袋调,得算。每个连接占大约2-4MB内存(thread_stack + 各种buffer),服务器能撑多少连接取决于还剩多少内存:

可分配连接数 ≈ (总内存 - Buffer Pool - 系统预留) / 单连接内存

典型经验值:

服务器配置 max_connections 建议
4核8G 300~500
8核16G 500~800
16核32G 800~1500

调完记得观察一段时间,别调完就走。


五、一个诊断脚本搞定排查

上面的信息量不小,紧急情况下手忙脚乱容易漏。我写了一个脚本,一键收集所有关键信息:

#!/bin/bash
# mysql_conn_diagnosis.sh
# 用法:bash mysql_conn_diagnosis.sh

SOCKET="/var/lib/mysql/mysql.sock"
OUTPUT="/tmp/mysql_conn_diag_$(date +%Y%m%d_%H%M%S).txt"

exec > >(tee "$OUTPUT") 2>&1

echo "====== MySQL 连接数爆满诊断 ======"
echo "时间: $(date)"
echo ""

echo "--- 1. 连接数概览 ---"
mysql -u root -S "$SOCKET" -N -e "
SELECT 'Threads_connected:', VARIABLE_VALUE FROM performance_schema.global_status WHERE variable_name='Threads_connected';
SELECT 'Threads_running:', VARIABLE_VALUE FROM performance_schema.global_status WHERE variable_name='Threads_running';
SELECT 'max_connections:', @@max_connections;
SELECT 'Max_used_connections:', VARIABLE_VALUE FROM performance_schema.global_status WHERE variable_name='Max_used_connections';
" 2>/dev/null

echo ""
echo "--- 2. 连接来源分布 ---"
mysql -u root -S "$SOCKET" -e "
SELECT USER, SUBSTRING_INDEX(HOST,':',1) AS SRC_IP, COUNT(*) AS CNT
FROM INFORMATION_SCHEMA.PROCESSLIST
GROUP BY USER, SRC_IP
ORDER BY CNT DESC LIMIT 10;
" 2>/dev/null

echo ""
echo "--- 3. 执行时间最长的查询 ---"
mysql -u root -S "$SOCKET" -e "
SELECT ID, USER, TIME, STATE, LEFT(INFO,120) AS QUERY
FROM INFORMATION_SCHEMA.PROCESSLIST
WHERE COMMAND != 'Sleep'
ORDER BY TIME DESC LIMIT 10;
" 2>/dev/null

echo ""
echo "--- 4. 未提交事务 ---"
mysql -u root -S "$SOCKET" -e "
SELECT trx_id, trx_state, trx_started,
       TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS RUNNING_SEC,
       LEFT(trx_query, 100) AS QUERY
FROM INFORMATION_SCHEMA.INNODB_TRX
ORDER BY trx_started LIMIT 10;
" 2>/dev/null

echo ""
echo "--- 5. 锁等待 ---"
mysql -u root -S "$SOCKET" -e "
SELECT COUNT(*) AS lock_wait_cnt FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_state='LOCK WAIT';
" 2>/dev/null

echo ""
echo "诊断完成,结果已保存到 $OUTPUT"

把这个脚本提前放在服务器上,出事的时候直接跑,不用现场想命令。


六、监控告警:别等爆了才知道

连接数爆满的可怕在于——等你发现的时候业务已经挂了。必须靠监控提前预警。

Prometheus + MySQL Exporter

mysqld_exporter 采集MySQL指标,核心关注这几个:

# 连接使用率
mysql_global_status_threads_connected / mysql_global_variables_max_connections

# 活跃线程数
mysql_global_status_threads_running

# 连接拒绝速率
rate(mysql_global_status_connection_errors_total[5m])

告警规则

# mysql_connection_alerts.yml
groups:
  - name: MySQL连接数告警
    rules:
      - alert: MySQLConnectionsHigh
        expr: mysql_global_status_threads_connected / mysql_global_variables_max_connections > 0.8
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "MySQL连接数超过80%"

      - alert: MySQLConnectionsCritical
        expr: mysql_global_status_threads_connected / mysql_global_variables_max_connections > 0.95
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "MySQL连接数即将打满,立即处理!"

      - alert: MySQLThreadsRunningHigh
        expr: mysql_global_status_threads_running > 20
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "MySQL活跃连接数过高,可能存在慢查询"

80%告警、95%严重——给处理留出缓冲时间。别设到99%才叫,那时候已经来不及了。

业务级心跳

光看连接数还不够。我习惯在Grafana面板上同时放三个指标:连接数趋势、活跃线程数、慢查询速率。三个一起看,才能判断是"连接没关"还是"SQL跑得慢"。


七、长期方案:参数 + 连接池 + 代码

止血和定位解决的是当下,长期不出问题得从三个层面做。

参数调优

-- 按服务器配置设合理的上限
SET PERSIST max_connections = 800;

-- 睡眠连接别占太久
SET PERSIST wait_timeout = 600;
SET PERSIST interactive_timeout = 600;

8.0的 SET PERSIST 很方便,改完立即生效且重启不丢。比改my.cnf安全。

上连接池

连接泄漏和短连接问题的根本解法是上连接池。Java用HikariCP:

spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000

maximum-pool-size 的经验值是 (CPU核数 * 2) + 磁盘数。8核机器配20左右差不多。别设太大,连接池太大会适得其反。

Python用内置的连接池:

from mysql.connector import pooling

pool = pooling.MySQLConnectionPool(
    pool_name="app_pool",
    pool_size=10,
    host="localhost",
    database="mydb",
    user="app_user",
    password="xxx"
)

def query(sql, params=None):
    conn = pool.get_connection()
    try:
        cursor = conn.cursor(dictionary=True)
        cursor.execute(sql, params or ())
        return cursor.fetchall()
    finally:
        cursor.close()
        conn.close()  # 归还到池,不是真断开

ProxySQL 做连接复用

如果应用代码改不动(老系统),可以在数据库前面放ProxySQL做连接复用:

-- ProxySQL管理端
mysql -h 127.0.0.1 -P 6032 -u admin -padmin

-- 查看连接池状态
SELECT * FROM stats_mysql_connection_pool;

-- 调整后端连接上限
UPDATE mysql_servers SET max_connections=100 WHERE hostgroup_id=10;
LOAD MYSQL SERVERS TO RUNTIME;

ProxySQL能扛住几千个前端连接,后端只开几十个到MySQL,相当于一个连接"漏斗"。


八、五个真实场景速查

场景 特征 处理
促销突发流量 连接数几分钟内飙升 临时调高max_connections + 杀Sleep连接 + 事后上连接池
慢查询堆积 Threads_running高,有长时间执行的SQL 找到慢SQL杀掉 + 加索引优化
连接泄漏 Sleep连接数持续增长,MAX_TIME很大 修复代码 + 定时清理脚本兜底
max_connections太小 Max_used_connections接近上限 算好内存后调大上限
ProxySQL自身瓶颈 通过ProxySQL连不上但直连正常 调整ProxySQL的pool_size和max_connections

九、面试怎么答

面试官:MySQL报Too many connections,你的处理流程是什么?

先止血再定因。第一步确认MySQL进程还活着,用root通过socket连接(root有预留连接位)。第二步杀掉超过30秒的Sleep连接释放连接位,临时调高max_connections让业务先恢复。第三步分析根因——看Threads_running判断有没有慢查询,看Sleep连接的来源IP和TIME判断有没有连接泄漏,看Max_used_connections判断上限是不是太小。第四步针对根因做修复:慢查询加索引,泄漏修代码,上限不够就按内存算一个合理值。最后配监控告警,80%预警95%严重,别等爆了才知道。

面试官:max_connections设多大合适?

不能拍脑袋,得算。每个MySQL连接约占2-4MB内存(thread_stack加各种buffer),用总内存减去Buffer Pool和系统预留,剩下的除以单连接占用,再留一半余量。8核16G的机器一般500到800。设太大也不行,连接太多CPU和锁争用会很严重,反而比连接不够还惨。

面试官:怎么判断是连接泄漏?

看Sleep连接的来源分布。同一个IP的Sleep连接数持续增长,而且连接存活时间很长(几千秒),基本就是泄漏。正常应用的连接用完就归还,Sleep时间不会太长。泄漏的典型原因是代码里拿到连接后没在finally里关闭,异常分支更容易漏。处理方案是上连接池加try-with-resources,再配合定时清理脚本做兜底。


避坑清单

序号 坑点 后果 正确做法
1 根本不开监控,等业务报障才发现 业务已中断几分钟甚至几十分钟 配Prometheus告警,80%就预警
2 max_connections盲目设到10000 内存爆了,CPU锁争用飙升,比连不上更惨 按内存算,留余量
3 杀Sleep连接不加时间条件 把正在等下一个请求的正常连接也杀了 加 TIME > 30 过滤
4 只止血不查根因 同样的问题反复发生 止血后必须分析是慢查询、泄漏还是配置问题
5 连接池size设太大 数据库压力反而更大,排队更严重 经验值:(CPU核数×2)+磁盘数
6 wait_timeout设太短(比如30秒) 连接频繁断开重建,MySQL握手开销大 一般600秒起步
7 应用代码不关连接,指望MySQL回收 连接泄漏积累到爆 try-with-resources或finally关闭
8 改了max_connections不改my.cnf或不用SET PERSIST MySQL重启后配置丢失 用SET PERSIST或改配置文件
9 出了事才想加监控 黄花菜都凉了 监控先行,上线前就配好
10 把连接数高等同于数据库挂了 盲目重启MySQL,可能丢数据 连接满不等于数据库挂,root能连就能救

总结

连接数爆满是"症状"不是"病根"。处理思路很简单:

止血 → 杀Sleep连接,临时调高max_connections

定因 → 慢查询?泄漏?短连接?上限太小?

治本 → 该加索引加索引,该修代码修代码,该上连接池上连接池

预防 → 监控告警先行,别等爆了才反应

记住一个优先级:先活下来,再查原因,最后治本。三个步骤都不能少,少了最后一步,同样的问题下周还会来。


我是数据库小学妹,咱们下篇见 👋

相关文章
|
27天前
|
SQL 监控 关系型数据库
数据库三大日志深度解析:Redo Log、Binlog、Undo Log 如何守护你的数据
本文由“数据库小学妹”带你厘清MySQL三大核心日志:Redo Log(引擎层物理日志,保障crash-safe)、Undo Log(支撑回滚与MVCC)和Binlog(Server层逻辑日志,用于复制与恢复),详解WAL机制与两阶段提交原理,助你真正理解事务安全底层逻辑。
|
27天前
|
人工智能 弹性计算 开发者
2026年阿里云618年中大促“AI加速季,智惠生产力”活动攻略详解
2026阿里云618大促(6.1–6.30)开启!主题“AI加速季,智惠生产力”,云服务器低至38元/年,Qwen3.7五折,满减券至高减1728元,首月0元AI产品、免费调用、百万Token补贴同步放送!
310 0
|
2月前
|
SQL 关系型数据库 MySQL
MySQL慢查询诊断实战:从10秒到0.1秒,我的5步排障法
数据库小学妹分享慢查询优化实战:从10秒降至0.08秒!详解「发现→收集→分析→优化→验证」5步排障法,覆盖慢日志配置、EXPLAIN进阶、索引失效场景、JOIN与分页优化等核心技巧,附真实案例与速查表。
|
27天前
|
API
阿里云微服务引擎 MSE 及 API 网关 2026 年 5 月产品动态
阿里云微服务引擎 MSE 及 API 网关 2026 年 5 月产品动态。
203 25
|
27天前
|
机器学习/深度学习 数据采集 人工智能
田间杂草检测数据集分享(适用于YOLO系列深度学习分类检测任务)
本数据集含4000张真实农田图像(小麦/玉米/水稻田),YOLO格式标注杂草目标,覆盖多天气、光照与视角,适用于YOLO系列等目标检测模型训练,助力智能除草与精准农业研究。(239字)
358 16
|
27天前
|
监控 API Windows
WGCLOUD v3.6.8 正式更新
WGCLOUD v3.6.8发布:修复CPU/内存等指标偶现为0、大屏离线数据不显示等Bug;新增Windows系统服务列表及开放API;优化告警脚本执行与SNMP设备运行时间兼容性。升级方式详见官方图示。
|
27天前
|
人工智能 缓存 弹性计算
阿里云服务器2核4G5M199元解析:独享型u1实例,性能、适用场景、购买和续费规则介绍
阿里云通用算力型u1实例(ecs.u1-c1m2.large)2核4G、5M带宽、80G ESSD Entry云盘,活动特惠价仅199元/年(官网价3498.36元),企业新老用户同享,续费同价至2027年3月31日,每人限购1台。该实例采用独享型架构,搭载Intel至强可扩展处理器,内网带宽1Gbit/s、收发包30万PPS、云盘IOPS 1万,性能稳定,适合企业官网、中小Web应用、轻量数据库及开发测试等场景。
|
27天前
|
安全 JavaScript 前端开发
《ZAKU渗透论:卓伊凡的2026渗透工程》第四章:Web攻击原理(下)——XSS、CSRF、文件上传漏洞
本章详解XSS、CSRF与文件上传三大Web漏洞:XSS通过注入恶意脚本窃取Cookie;CSRF伪造已登录用户请求执行非自愿操作;文件上传漏洞则因校验缺失致服务器被控。三者共性——过度信任用户输入。(239字)
341 10
|
27天前
|
SQL 人工智能 关系型数据库
AI Agent 混合检索选型:阿里云 AnalyticDB MySQL 向量+全文一站式方案
阿里云AnalyticDB MySQL版是面向AI Agent/RAG场景的一站式混合检索数据库,原生支持向量检索+全文搜索+结构化查询,单SQL实现三合一。延迟<10ms,成本降60%+,开发提效3倍,显著优于Milvus+Elasticsearch多组件架构。
263 6