
一个周末晚上我看到某服务群里的告警又亮了提示RDS连接数超过阈值紧接着业务方开始反馈“页面打不开”“接口超时”。我打开监控面板连接数曲线已经拉满成一条平线。这种场景不少DBA和运维应该都经历过明明应用没发新版本数据库CPU和内存也不算高连接数却突然冲到上限然后整个服务像被扼住喉咙一样动弹不得。这里的“连接数”就是RDS监控里最常见、也最容易让新手一头雾水的指标之一。它到底在统计什么、为什么会满、满了为什么影响这么大这篇文章我把自己的理解、排查过程和调优经验一起捋一遍。我平时接触最多的就是云厂商的RDS实例包括MySQL和PostgreSQL两套体系。连接数这个监控项两个引擎都有含义也基本一致它表示当前实例上建立的客户端会话数量。说得直白一点就是“此时此刻有多少条会话连在你的数据库上”。但“有多少条会话”背后牵扯的东西非常多包括连接池配置、应用线程数、空闲会话回收策略、数据库实例规格等等。只看一个数字很容易误判把这个指标和CPU、慢查询、活跃会话放在一起看才能知道数据库到底是不是真的撑不住了。1. 连接数监控到底在监控什么1.1 一个真实的“连接数打满”事故先讲一个我排查过多次的现象。某天业务高峰期RDS监控面板里的连接数从几十条突然飙升到几千条直接顶到实例上限。当时实例规格是4核8G最大连接数配的是4000。从曲线看连接数上升几乎是直线拉上去的不是慢慢爬坡而是几分钟内从几百暴涨到4000。当时我的第一反应不是看SQL而是先看活跃连接数和空闲连接数的拆分。在数据库里执行了一条语句SHOW STATUS LIKE Threads_connected; SHOW STATUS LIKE Threads_running;Threads_connected显示3972Threads_running却只有12。这说明绝大部分连接都是“挂”在那边但没有真正执行SQL。这种状态通常不是数据库本身的问题而是应用侧把连接建起来之后没有及时释放。后来查下来是某个Java服务在发新版本时连接池参数被重置连接池最大连接数调得很大又忘了设置空闲超时导致一批连接建立后就不回收活活把数据库连接数堆满。这个案例很典型。RDS监控面板上的连接数如果只是单纯打着“Label连接数”的指标它并不会直接告诉你这些连接是活跃的还是空闲的是健康的还是泄漏的。需要我们主动去剖析。1.2 连接数的本质一条连接的生命周期数据库连接数这个指标从根本上讲反映的是“客户端与数据库之间的TCP会话数量”。一次完整的连接生命周期大概是这样的客户端发起TCP握手数据库校验账号权限建立会话客户端执行SQL语句然后断开连接服务端回收会话资源。每次连接都需要占用数据库端的内存、文件描述符、线程或进程资源。在MySQL里每个连接通常会对应一个线程连接数太多意味着线程上下文切换频繁内存占用升高。即使这些连接什么都不做也会吃掉资源。在PostgreSQL里每个连接是一个独立进程连接数爆掉的后果更直接——进程数太多内存翻倍增长甚至触发OOM。所以RDS监控里的连接数不是简单给你看一个“热闹程度”的数字它是衡量实例资源是否被连接层“拖死”的关键指标。连接数设置得越大不代表实例越能扛连接数长期过高反而说明应用侧连接管理存在隐患。2. 连接数被占满的几种典型路径2.1 应用层没释放连接最常见也是最好解决的连接泄漏是我在所有连接数故障里遇到最多的一种。典型场景是应用程序里获取了数据库连接但finally块里没有关闭或者关闭逻辑抛了异常被吞掉。一次两次没问题积累到一定量级连接数就蹭蹭往上涨。排查方式我一般这样操作先看应用日志里有没有连接获取超时、连接池耗尽之类的关键字。如果没有再看连接池监控。Java应用最常见的Druid、HikariCP都有监控指标能看到当前活跃连接数、空闲连接数、等待获取连接的线程数。HikariCP的设置中有一个关键参数叫maximumPoolSize如果直接配成数据库允许的最大连接数应用一启动加上多实例部署连接数瞬间就能爆。合理做法是让所有应用实例的连接池上限总和小于RDS实例的最大连接数比如RDS配4000应用实例有10个每个连接池最大配300这样加起来是3000留出1000的余量给管理操作、备份、临时查询等场景。还有一个容易忽略的点连接池虽然会回收空闲连接但不同类型的连接池默认行为不一样。HikariCP默认能很好地处理空闲连接回收而某些老版本Druid如果minIdle设置得太大空闲连接也会一直保持在池子里导致连接数偏高。这个时候调整minIdle比调整maxPoolSize更有效。2.2 连接池配置超出实例规格资源没爆但连接先爆连接数上限和实例规格有关系。RDS MySQL通常会根据实例内存大小给出一个默认的最大连接数比如4G内存的实例默认最大连接数可能在4000左右。实际能支撑多少还要看每条连接本身占用多少内存以及库表缓冲池等占了多少内存。如果实例内存只有8G你把应用连接池的maxActive调成5000理论上RDS上限可能允许但内存早就被吃光了数据库会频繁进行内存交换性能反而下降。那么问题来了连接数上线到底调多少合适我习惯用这个思路估算最大连接数 ≈ (实例可用内存 - 缓冲池占用 - 其他开销) / 单连接平均内存占用单连接平均内存占用在MySQL里不太好精确计算但可以从监控数据里反推。实例内存8G缓冲池配了4G剩下4G给连接和其他开销。如果观察到一个连接大约占用2-4MB内存那上限设在1000左右比较稳妥。很多人只盯着RDS控制台默认的最大连接数参数却忽略了实例内存这个根本约束等到连接数报警才发现不是连接数太多而是实例内存太小。2.3 慢查询拖住连接不放手连接数高但活跃连接数也高这个时候不要把锅全甩给连接池得看SQL了。我经常遇到的一种情况一段非常慢的SQL要跑20多秒期间连接一直被占用客户端等不到结果只能再发起新的连接。数据库连接数也跟着水涨船高。这种情况下连接数只是表象真正的问题是慢查询。排查时先看有没有大量的慢查询日志尤其关注那种长时间处于“Sending data”或“executing”状态的会话。然后看InnoDB的行锁等待和元数据锁等待很多慢SQL不是真正的慢而是在等锁。用下面这条SQL可以快速查看当前正在执行的会话和状态SELECT id, user, host, db, command, time, state, info FROM information_schema.processlist WHERE command ! Sleep ORDER BY time DESC;如果看到很多连接都卡在同一个表的查询上并且state为“Waiting for table metadata lock”或者“Updating”多半是DDL和DML互相阻塞导致的。这比单纯看连接数曲线更有价值。2.4 突发流量和“连接风暴”这种场景在秒杀、抢购、活动营销时比较常见。前一秒连接数正常后一秒流量冲进来每个请求都去连接池获取连接连接池不够用就新建连接数据库端连接数跟着暴涨。如果RDS的最大连接数有限就会出现连接排队、超时、报错。连接风暴的特点是不会持续太久但冲击力强。它不像连接泄漏那样一直累积而是短时间内把连接数顶到上限。这个时候如果应用有缓存可以先挡一波如果没有缓存就要检查数据库侧是否开启了连接压缩、复用等能力。还有一个常规操作是让应用侧的连接池提供一个“等待队列”不要无限新建连接而是让请求排队等待可用连接。这样数据库的压力更平缓不会被打满到完全不可用。3. 如何判断“连接数告警”是真故障还是假警报3.1 先看监控曲线的“形态”再说很多人一看到连接数告警就开始重启应用、清连接或者盲目调大最大连接数。我的建议是先花30秒看一下连接数曲线的形态再决定下一步。连接数曲线的形态大致有三类阶梯上升型每隔一段时间涨一截像爬楼梯一样。这种通常是连接泄漏或者定时任务未释放连接。脉冲型突然飙高过一会儿又降下来。这种通常是突发流量或某个批量任务引起数据库本身可能没问题。持续高位型曲线平稳地保持在高位和业务高峰明显相关。这种往往是连接池配置偏大或者业务并发确实高。如果是脉冲型我不太担心等它降下来就好。如果是阶梯上升型或持续高位型就必须深入排查了。3.2 用SQL和命令确认当前连接状态云厂商的RDS控制台一般都有实时会话功能能看到当前的连接列表。但更灵活的方式还是登录数据库直接查询。MySQL可以用performance_schema也可以查information_schema。常用的几条SQL-- 查看当前总连接数 SELECT COUNT(1) FROM information_schema.processlist; -- 查看不同用户连接数分布 SELECT user, host, COUNT(1) FROM information_schema.processlist GROUP BY user, host; -- 查看连接来源IP分布 SELECT SUBSTRING_INDEX(host, :, 1) AS ip, COUNT(1) FROM information_schema.processlist GROUP BY ip ORDER BY COUNT(1) DESC;这些SQL能快速定位到“是谁把连接数打满的”。我遇到过一个情况排查了半天发现是内部一个数据同步工具用了一个很老的账号每5分钟连一次数据库但一直没断开。找到来源后直接在账号层面限制它的最大连接数问题就解决了。3.3 区分“空闲连接”和“活跃连接”只看连接总数容易误判必须看活跃连接数。活跃连接数才是真正在干活的连接空闲连接只是占着茅坑不拉屎。监控面板上如果只有总连接数指标建议把它和Threads_running、SlowQueries放在同一张图表里看。如果总连接数高但活跃连接数很低这是连接池配置或连接泄漏问题。如果总连接数和活跃连接数同时高并且慢查询数也在涨这是SQL性能问题。区分这两者对后续处理方式影响很大。连接泄漏需要改代码、调整连接池参数SQL性能问题则需要优化SQL、加索引或改写业务逻辑。用错方案不仅解决不了问题还会让故障时间拉长。有一次同事把连接泄漏问题当成慢SQL问题处理反复加索引没用最后我帮他一查发现是应用层没有正常归还连接连接池配置也有问题。方向错了越努力越尴尬。4. 从监控到治理连接数的日常调优手段4.1 连接池参数怎么调才叫“合理”连接池调优的核心不是把数值调大而是在“响应速度”和“资源占用”之间找平衡。连接池太小请求会排队连接池太大数据库资源会被连接占满。通用的参考公式是连接池大小 应用实例数 × 单实例连接池最大连接数这个结果建议控制在RDS最大连接数的70%左右剩下的30%留给DBA日常运维、数据备份、报表查询和突发情况。数据库不是越“满”越好总得留点余量。常用的连接池参数以HikariCP为例我会重点调这几个参数作用我的建议maximumPoolSize池中最大连接数按实例数和数据库上限综合评估minimumIdle池中最小空闲连接数等于或略小于maximumPoolSize避免频繁创建连接connectionTimeout获取连接超时时间30000ms左右太短容易误报idleTimeout空闲连接超时600000ms一般不需要太短maxLifetime连接最大存活时间1800000ms建议小于数据库wait_timeoutvalidationTimeout连接有效性检测超时5000ms这里有个细节容易被忽略maxLifetime要小于数据库侧的wait_timeout或interactive_timeout。如果数据库把空闲连接断开了而连接池不知道下次拿到的就是一条失效连接反而增加报错概率。4.2 应用层的优雅释放与超时设置连接池参数调好了应用代码也得配合。最基础的一点是所有数据库操作必须放在try-with-resources或者try-finally中确保连接用完就归还。Java的try-with-resources是个好习惯try (Connection conn dataSource.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { // 业务逻辑 } } catch (SQLException e) { // 处理异常 }很多连接泄漏就是漏了finally或异常分支导致的。另外不要在一个事务里执行慢查询。事务执行时间越长连接占用时间越长连接池很容易被打满。还有一个容易忽略的点如果应用需要并发执行大量数据库操作不要盲目增加连接池大小来提升并发度。更好的办法是减少单个操作耗时比如批量插入、批量查询、合理使用索引。只有一个SQL快起来连接才能尽快释放。4.3 数据库侧的连接数限制与预防除了调应用数据库侧也可以做一些预防。首先RDS实例本身有最大连接数参数MySQL对应的是max_connections。这个值不是拍脑袋设的前面说了要和实例内存匹配。其次可以为不同业务账号设置不同的连接数限制。在MySQL 8.0里可以用ALTER USER app_user% WITH MAX_USER_CONNECTIONS 200;这样即使某个业务出了问题也不会把整个实例的连接全部占满其他业务至少还能撑住。这个操作在云RDS上通常也支持只是需要足够的权限。同时合理设置wait_timeout和interactive_timeout让空闲连接尽快被回收。一般建议设置在60秒到300秒之间太短会让连接频繁断开重建太长则会让连接泄漏问题更加隐蔽。我还会建议开发环境里针对长期空闲连接做一个巡检脚本定期杀掉超过一定时间还没释放的会话。生产环境不建议直接杀会话怕误伤正常事务但可以报警。5. 常见问题与排查技巧实录5.1 问题速查表连接数告警的常见场景和处理方向这里整理几类我处理过的连接数告警问题写成速查表方便你在故障时快速对照。现象可能原因排查动作处理方向连接数持续上涨无回落连接泄漏看应用日志、连接池监控修复代码、释放连接连接数突增与流量高峰吻合高并发查实时会话、活跃SQL连接池排队、限流连接数高Threads_running也高慢SQL或锁等待查processlist、慢查询日志优化SQL、调索引连接数高活跃连接很低空闲连接过多查processlist中Sleep状态调小minIdle、缩短wait_timeout某个账号占用了大量连接账号连接数过高按user分组统计限制用户最大连接数频繁报“Too many connections”总连接数达上限查看max_connections评估实例规格或调大上限但先排查连接是否合理重启应用后短暂恢复又爆满连接池配置过高检查多个实例连接池总和下调连接池最大连接数排查连接数问题一定不要忽略“时间线”。把连接数曲线和应用发版时间、变更时间对齐很多问题都是变更引起的。我发现一个规律没有发版、没有变更的情况下连接数很少会无故爆掉。一旦连接数异常优先问一句“最近发了什么改了什么地方”十有八九能命中。5.2 一个我印象很深的案例连接数打满但业务量并不高有一次同事反馈RDS连接数突然打满但业务量并没有明显增长。我看了下监控连接数在几分钟内从几百冲到3000多但CPU和活跃连接数都不高。按常规思路怀疑连接泄漏但查了应用日志没有任何报错。后来我登录RDS查processlist发现大量连接都来自同一个IP而且user都是一个内部账号。再追了一下发现是数据组那边跑了一个Python脚本脚本里用了多线程每个线程都创建了自己的数据库连接但线程结束的时候连接没有显式关闭。Python脚本本身生命周期短但架不住多线程批量跑连接数一下子就堆起来了。这个案例给我两个启示第一连接数监控不只盯着应用服务器还要关注定时任务、脚本、数据同步任务等非典型客户端第二监控告警的阈值要分场景设置比如给普通业务的账号设置连接数告警也给数据同步账号设置单独的告警避免等到整个实例连接数爆了才收到通知。5.3 我常用的三个“体检式”巡查命令日常巡检中我会定期执行这几条命令提前发现隐患而不是等告警响了再处理。第一条检查当前连接数和使用率SHOW VARIABLES LIKE max_connections; SHOW STATUS LIKE Threads_connected;第二条查看连接状态分布SELECT command, COUNT(1) FROM information_schema.processlist GROUP BY command;如果Sleep数量占比特别高说明空闲连接太多需要调连接池参数。第三条查看每个客户端IP的连接数SELECT SUBSTRING_INDEX(host, :, 1) AS client_ip, COUNT(1) AS cnt FROM information_schema.processlist GROUP BY client_ip ORDER BY cnt DESC;这三条命令执行成本极低但能快速摸清实例的连接健康度。我习惯把它们写成一个脚本每天定时跑一次输出结果推送到工作群。大家都说这个“体检”挺有用的。6. 一些关于连接数阈值设置的体会不同业务对连接数的敏感度不一样告警阈值不能照搬默认值。我见过有的团队把连接数告警阈值设成最大连接数的80%业务高峰期经常误报告警过多以后大家就不重视了真正出问题反而没人看。也见过阈值设得太低连接数稍微波动就报警白白消耗精力。经验做法是设置两级告警第一级连接数达到最大连接数的60%时提示“注意观察”第二级达到80%时触发“紧急告警”。同时还要设置一个“连接数变化率”监控如果连接数在短时间内增长过快比单纯看绝对值更容易发现问题。另外RDS监控的连接数指标最好和应用层的连接池指标打通。应用连接池一般有“active连接数”“idle连接数”“pending获取连接数”等指标这些指标和数据库端的连接数是上下游关系。只看数据库端看不出来是哪个应用打满的。我在实践中会把应用连接池的核心指标接入到监控系统里这样一旦出现连接数异常就能快速定位到具体应用实例。否则一个个排查只能靠猜效率太低了。连接数只是RDS监控众多指标中的一项但它和业务稳定性的关联度非常高。很多故障看起来是数据库问题核心却是连接管理问题。对我个人来说处理连接数告警已经形成了一套“看曲线、查会话、查来源、调参数”的固定流程。每次把连接数治理好数据库CPU、内存、响应时间都会跟着受益。这篇文章算是我在这方面的一个梳理希望能帮到还在被连接数告警折腾的朋友。