收到“Too many connections”告警后,最容易犯的错误是立刻重启数据库或盲目调大连接上限。这样可能暂时恢复,却同时销毁了定位根因最有价值的现场。

先定一条现场原则

业务仍有部分请求可用时,优先限流、隔离异常实例或缩小影响面;除非数据库已经完全失去响应,否则不要把“重启”作为第一步。

一、先确认:连接数高,不等于数据库忙

连接相关问题至少分成三类,处理动作完全不同:

现象典型指标优先判断
连接很多但大多空闲Threads_connected 高,Threads_running 低连接池过大、空闲连接回收异常
连接与运行线程都高Threads_running、CPU、IO 同时上升慢 SQL、流量突增或资源瓶颈
连接持续排队或超时锁等待上升,业务 RT 拉长长事务、热点更新或元数据锁

二、8 步排查路径

第 1 步:确认影响范围与时间点

先回答四个问题:从几点开始?哪些接口受影响?是所有数据库实例还是单实例?此时是否有发布、批任务、营销活动或流量切换?把告警时间与变更时间对齐,能够快速缩小搜索范围。

  • 记录告警开始时间、峰值和当前值
  • 确认错误率、P95/P99 延迟与受影响接口
  • 检查近 30 分钟发布、配置和定时任务变更

第 2 步:查看连接水位与线程状态

不要只看一个 max_connections。把当前连接、活跃线程、历史峰值和拒绝次数放在一起判断。

SHOW VARIABLES LIKE 'max_connections';
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW GLOBAL STATUS LIKE 'Threads_running';
SHOW GLOBAL STATUS LIKE 'Max_used_connections';
SHOW GLOBAL STATUS LIKE 'Connection_errors_max_connections';

判断重点:Threads_connected 接近上限只是水位问题;Threads_running 持续高位才更接近数据库正在承压。

第 3 步:识别连接来自谁、在做什么

SHOW FULL PROCESSLIST;

SELECT USER, HOST, DB, COMMAND, COUNT(*) AS sessions
FROM information_schema.PROCESSLIST
GROUP BY USER, HOST, DB, COMMAND
ORDER BY sessions DESC;

重点关注某个应用账号、来源主机或数据库是否突然占据大多数连接,以及 Sleep、Query、Locked 等状态的分布。

第 4 步:检查应用连接池

如果空闲连接占多数,回到应用侧核对每个实例的最小连接数、最大连接数、连接超时和空闲回收时间。总连接预算要按实例数计算:

连接预算公式

应用总连接上限 = 单实例连接池上限 × 应用实例数 × 数据源数量

还要为运维、备份、监控、复制和故障切换预留安全余量。

第 5 步:检查慢 SQL 与事务堆积

连接被慢 SQL 长时间占用时,应用会不断申请新连接,最终把池和数据库一起打满。将 processlist、慢日志和 performance_schema 中的高耗时语句关联起来,而不是只凭一条 SQL 猜测。

SELECT EVENT_NAME, COUNT_STAR,
       ROUND(SUM_TIMER_WAIT / 1000000000000, 2) AS total_seconds
FROM performance_schema.events_waits_summary_global_by_event_name
WHERE COUNT_STAR > 0
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 15;

第 6 步:检查锁等待与长事务

锁等待会让 SQL 不消耗太多 CPU 却迟迟不返回,连接数因此持续累积。结合版本可用的 performance_schema 事务和锁表,确认阻塞者、等待者以及事务持续时间。

SELECT trx_id, trx_mysql_thread_id, trx_started,
       trx_state, trx_rows_locked, trx_query
FROM information_schema.innodb_trx
ORDER BY trx_started;

第 7 步:核对系统资源与数据库容量

同时检查 CPU、内存、磁盘延迟、IOPS、文件描述符和网络重传。如果 Threads_running 高但 CPU 不高,重点看 IO 与锁;CPU 打满则继续区分 SQL 计算、上下文切换和其他进程竞争。

第 8 步:止损、验证与恢复

  1. 先缩小影响面:暂停异常批任务、对问题接口限流,或摘除异常应用实例。
  2. 再处理根因:终止明确异常且可安全回滚的会话,修正连接池或慢 SQL。
  3. 观察恢复:连接水位、运行线程、错误率和 P95 同时恢复才算有效。
  4. 最后复盘:补齐连接预算、告警阈值、容量压测和应急脚本。

三、什么时候可以临时调大 max_connections?

只有在确认数据库还有 CPU、内存和文件描述符余量,且当前问题是短时流量或连接预算不足时,临时调大才可能帮助止损。它必须配合过期时间和后续修复计划,不能成为永久答案。

恢复完成的四个验证点

错误率回落、P95 恢复、Threads_running 回归基线、连接数不再持续增长。四项同时满足,再宣布故障恢复。

四、常见误区

  • 直接重启 MySQL:现场消失,未提交事务和恢复时间可能扩大影响。
  • 只看连接总数:忽略活跃线程、锁等待和连接来源。
  • 只改数据库上限:应用实例扩容后,连接池总预算再次失控。
  • 故障恢复就结束:没有补容量模型、告警和演练,下次仍会重复。

五、一句话记忆

先确认影响,保护现场;再分清连接多还是线程忙;沿连接来源、慢 SQL、锁和资源逐层排查;最后用业务指标验证恢复。