
先说个有点反直觉的结论GaussDB轻量化版本25.1.30内核版本505.2.1如果出现CPU、内存使用率都不高但数据库连接数被打满、应用连不上库的情况大概率不是资源瓶颈而是连接管理层出了问题。这里说的连接管理层包括应用连接池配置、数据库会话槽位、空闲回收策略以及轻量化版本默认参数这几个维度。这篇文章是我一次真实排障过程的完整记录。从现象确认、连接状态分类、轻量化版本参数暗坑到最终的根治方案和复盘建议我都按实际操作的顺序写出来。不管你现在正守着GaussDB轻量化环境还是准备把标准版迁移到资源受限场景这套排查链路基本都能直接复用。1. 故障现场还原CPU内存都很闲连接池却先绷不住了1.1 从一句“数据库连不上”开始业务方报障时给的信息不多就一句话“应用报错获取数据库连接失败连续重试也不行。”我当时第一反应是查常规性能指标CPU、内存、磁盘IO、慢SQL、锁等待过了一遍全都没有异常。服务器CPU使用率长期在20%上下内存也没到告警线怎么看都不像需要救火的状态。但我用gsql连进数据库执行了一条查询之后问题立刻清楚了SELECT count(*) FROM pg_stat_activity;结果直接顶到了上限。再看参数SHOW max_connections;两个数字一对比连接数已经贴边了新连接根本排不上位置。所谓的“连不上数据库”本质上就是数据库的会话槽位被占满了。这里有个很容易忽略的细节连接数打满时不同的驱动和框架报错形式完全不一样。有的直接报“too many clients already”有的是“remaining connection slots are reserved for non-replication superuser connections”还有的是应用侧连接池抛出“connection pool exhausted”。报错长得五花八门但根源都指向同一个问题连接槽位用完了。1.2 资源空闲和连接打满为什么能同时发生CPU和内存代表的是计算资源和内存资源连接数却是一个相对独立的管理维度。用一个收费站类比CPU和内存相当于高速公路本身连接数则像是收费站的通道数量。道路再空旷如果每个通道都被不走车的车占着后面的车照样进不了收费站。空闲连接几乎不消耗CPU它占用的主要是一个会话槽位、一份内存以及可能的线程资源。当应用侧的连接池创建了连接但迟迟不归还或者数据库侧的空闲回收策略没有真正生效这些连接就会一直蹲在槽位上。服务器看起来非常“闲”实际连接资源已经耗尽。这个场景在轻量化部署环境里更容易出现。轻量化版本本身就是面向资源受限场景发布的默认的max_connections通常比标准版保守。如果应用侧连接池还是按标准版的习惯去配连接数打满只是时间问题。2. 第一刀先分清是数据库会话数超限还是操作系统的承载到顶2.1 数据库侧的max_connections和当前连接数怎么看排障的第一步永远是确认数字。连接数打满最大的尴尬是应用已经连不进去了但管理员往往还能进去。原因是GaussDB在max_connections之外会预留一小部分槽位给超级用户和运维连接。所以出现“普通应用连不上、DBA却能登进去”的情况不要觉得奇怪这是保护机制在起作用。管理员登录后执行SHOW max_connections; SELECT count(*) AS total_conn FROM pg_stat_activity;然后按会话状态做个分组这一步非常关键SELECT state, count(*) FROM pg_stat_activity GROUP BY state ORDER BY 2 DESC;如果idle占了大头说明连接建了没回收。如果active占大头说明确实在处理业务那要进一步看慢SQL和并发情况。如果idle in transaction占大头问题就更麻烦了这些连接不仅占着槽位还拖着事务快照时间长了连数据库的清理工作都会受影响。我当时看到的现象是idle连接占比超过70%而且很多连接建立了很长时间application_name指向的是应用连接池。看到这个结果我心里基本有数了——不是业务量大是连接管理配合出了问题。2.2 操作系统层文件描述符和连接队列也可能成为隐形瓶颈虽然我们这次确认瓶颈在数据库侧但排查时必须把操作系统层过一遍否则容易漏判。数据库连接在操作系统层对应一个socket句柄也受文件描述符数量限制。常用的检查命令# 查看当前进程可打开的文件句柄上限 ulimit -n # 查看系统当前的TCP连接状态统计 ss -s # 查看指定端口的连接数量 netstat -anp | grep port | wc -l重点看两部分一是ulimit -n的值如果这个值很小连接还没走到数据库就被系统拒了二是有没有大量TIME_WAIT或CLOSE_WAIT堆积。TIME_WAIT多通常是短连接高并发导致的CLOSE_WAIT多则往往说明应用没有正常关闭连接。轻量化部署的服务器配置本身可能不高sysctl参数和ulimit往往也没人专门调过排障的时候顺手确认一下能排除掉不少干扰项。2.3 确认上限后先算这台轻量化节点的内存账连接数打满之后很多人的第一反应是“调大max_connections”。我的建议是先算内存账再做决定。GaussDB每个连接都会分配一定的私有内存即使空闲连接也不会被完全释放。轻量化版本本身就把内存预算卡得比较紧直接把max_connections调上去可能临时止了血转头内存使用率就开始飙升。我当时先统计了已有连接占用的内存和系统整体内存余量简单估算方式是这样估算连接占用内存 ≈ 当前连接数 × 单连接平均内存开销单连接平均内存开销可以从数据库监控里捞不同规格、不同参数配置下差异很大。如果内存余量充足可以适当调高max_connections如果余量不足宁可先清理异常连接也不要无脑扩容连接数。这条原则在轻量化环境里尤其重要资源本来就紧更不能拆东墙补西墙。3. 第二刀用pg_stat_activity给连接“称重”找到谁占着茅坑不拉屎3.1 把连接按状态归堆问题基本就现出原形了GaussDB的pg_stat_activity视图和PostgreSQL生态很接近很多常用查询可以直接复用。连接数打满后最先要做的是把连接状态分类SELECT state, count(*) FROM pg_stat_activity GROUP BY state ORDER BY 2 DESC;各状态代表的意义要心里有数active正在执行SQL这类连接是“真忙”。如果active很多要结合慢SQL日志看是不是有烂SQL在拖。idle连接空闲没有正在执行的SQL。连接池里有大量空闲连接是正常的但空闲连接占比过高且持续不回收说明连接池或者数据库回收策略有问题。idle in transaction处于事务中但没有执行下一步。这类连接最危险它占着槽位、占着事务快照还会拖累数据库的清理机制。如果这个状态的连接持续堆积往往和代码里开了事务没提交/没回滚有关。fastpath function call等其他状态不同版本显示不同同样要单独看。我们这次就是通过这个查询一眼看出idle连接是绝对主力。当时服务器有三个应用实例每个实例连接池都建了一堆空闲连接而且这些连接几乎不释放。3.2 从backend_type和等待事件里找深层原因状态归堆之后还可以再深入一层看看连接的来源和等待情况SELECT backend_type, state, wait_event_type, wait_event, count(*) FROM pg_stat_activity GROUP BY backend_type, state, wait_event_type, wait_event ORDER BY count(*) DESC;backend_type能区分client backend和后台进程。这里有个容易误判的点部分监控工具统计的是pg_stat_activity总行数但这个视图里其实混着autovacuum worker、walsender等后台任务。如果后台进程也占了不少行数就会造成“连接数占用率很高”的假象。排查的时候最好过滤掉后台进程只看真正的客户端连接SELECT state, count(*) FROM pg_stat_activity WHERE backend_type client backend GROUP BY state ORDER BY 2 DESC;如果开了流复制或者逻辑复制replication连接也要单独算进连接预算里这部分连接平时不起眼但在连接数逼近上限时会成为压垮骆驼的最后一根稻草。3.3 连接泄漏的典型现场QPS不高但连接数天天涨连接泄漏是“使用率不高但连接打满”最常见的幕后黑手症状非常有特点连接数缓慢爬坡今天100明天120后天150到了某个峰值点直接打满。因为业务量不大CPU和内存一直很稳定应用侧也没有明显报错直到连接数撞线才发现。定位连接泄漏的核心思路是看连接的年龄和归属SELECT pid, state, application_name, client_addr, backend_start, now() - backend_start AS conn_age FROM pg_stat_activity WHERE backend_type client backend ORDER BY conn_age DESC;如果一个连接的backend_start非常早而且已经idle了很久那基本可以判断是某个应用连接没有正确关闭。再配合application_name和client_addr能直接定位到是哪个实例、哪个模块干的事。如果连接池有监控比如HikariCP的metrics、Druid的StatFilter也可以看连接池里的活跃连接和空闲连接走势。不过很多系统的连接池监控并没有接入这时候数据库侧的诊断就显得格外重要。3.4 别忘了认证慢和中间层连接排队还有一种情况max_connections明明还有空位但应用就是连不上。这个时候要考虑认证链路拖慢。一个连接从建立到可用要经历TCP建链、SSL握手、认证鉴权、分配会话几个步骤任何一个环节慢都会造成应用侧的连接请求排队堆积。排查方式看数据库日志里有没有大量认证超时或失败记录看是否配置了SSL证书校验耗时是否正常如果前面挂了中间件还要看中间件自身的连接池是否在排队。我们这次场景里认证环节没有异常所以后续精力主要花在会话空闲和回收策略上。但这条线没有排除之前我不会轻易下结论避免排查方向跑偏。4. 轻量化版本暗坑默认参数、线程池逻辑和内核505.2.1的特殊行为4.1 默认参数先卡住你这是轻量化部署的第一课轻量化版本的定位是“在有限资源里能跑起来”所以很多参数默认值会比标准版保守。以max_connections为例标准版可能给到几百甚至上千轻量化版默认往往只有一两百甚至更少具体要看实际部署规格和应用配置。建议先核对以下几个参数参数作用排查重点max_connections数据库最大连接数是否与应用连接池总量匹配idle_in_transaction_session_timeout事务内空闲超时是否存在大量idle in transaction会话session_timeout空闲会话超时空闲连接是否会被自动回收enable_thread_pool线程池开关是否把并发压力转移到线程维度max_prepared_transactions两阶段事务上限是否有残留prepared事务占据槽位不同小版本的参数名可能有差异以实际执行SHOW的结果为准。轻量化部署下这些参数往往不会有人专门调过遇到问题时要先确认它们是不是默认值。4.2 线程池和连接池不是一回事别调错对象“连接打满”很容易让人想到调大max_connections但在GaussDB里还涉及线程池的逻辑。连接池、会话槽位、线程池是三层不同的概念应用连接池应用侧维护的一堆数据库连接主要目的是复用连接、降低建连开销。数据库会话槽位数据库侧能同时承载的会话数量上限由max_connections控制。线程池数据库内部处理SQL的线程资源由线程池相关参数控制。CPU不高但连接数打满时问题通常出在“应用连接池配置过大”或者“会话回收慢”不是线程池不够。反过来如果CPU打满、并发SQL很多才应该优先看线程池参数。轻量化版本的线程池模式和标准版也可能有差异每个会话对应的线程开销需要格外注意。连接数调得过大线程池规格会跟着变大内存和CPU都会被拖累。所以我的原则是先控制连接总量再考虑并发处理能力两头不能都开大。4.3 内核505.2.1上我注意到的两个容易误判的点先说结论我不打算下“这个版本有bug”的结论但从实际环境来看有两个容易把排查方向带偏的点值得留意。第一是连接数统计口径。有些监控看的pg_stat_activity总行数混入了后台进程和内部任务。排查时要用backend_typeclient backend过滤只看真正的客户端连接否则会出现“连接数占用90%但业务连接只有一半”的假象。第二是空闲回收参数的组合生效问题。只设置了session_timeout但如果应用连接池一直在做心跳或保活查询数据库会认为这些连接不是空闲的回收逻辑就一直不触发。排查时要连接池心跳间隔和数据库回收参数一起看两者的时间差要给足。这两个点在我们这次排障中都有体现也是我建议所有轻量化环境使用者提前注意的地方。5. 根治方案应用连接池、数据库回收策略、监控告警一起调5.1 应用侧连接池水位到底怎么定先把结论给出数据库连接数预算必须集中规划不能每个应用各配各的否则一定会出问题。推荐公式如下每个实例连接池上限 ≤ (max_connections × 预留比例 - 后台/运维连接数) / 应用实例数举例假设max_connections 200预留20%给运维和后台任务可用业务连接约160。应用有4个实例每个实例最大连接数就不要超过40。如果之前每个实例配了100连接数打满就是必然结果。如果再结合连接池的具体参数一般关注这几个maximumPoolSize按上面的公式计算不要取整取大。minimumIdle建议比最大值小一些避免频繁创建和销毁连接。idleTimeout要比数据库的空闲回收时间短一点让连接池先主动回收不要等数据库来杀连接。maxLifetime要小于数据库和中间件的连接超时时间防止连接被“杀”后再回写。connectionTimeout不能设太长否则数据库不可用时应用线程会全部卡在等待连接上。这些参数在不同连接池里名称略有差异但思路是一样的。连接池不是越大越好它和数据库的容量是一套联动系统。5.2 数据库侧的空闲回收怎么配才不误伤数据库侧的回收策略重点看两个方向事务内空闲超时和普通空闲会话超时。如果确认应用连接池不会主动释放空闲连接建议数据库侧开启兜底回收。比如把事务内空闲超时设成5分钟普通空闲会话超时设成10分钟。再配合连接池的idleTimeout一起使用问题会缓解很多。但这里有一个坑连接池如果配了心跳机制每30秒ping一次数据库那么session_timeout可能永远等不到触发条件。数据库认为连接是“活着”的所以不回收。解决办法是让连接池的idleTimeout或validationTimeout小于数据库回收时间让连接池先回收空闲连接数据库回收机制只作为最后兜底。我当时调整后的配置思路大致是这样层级配置项建议方向连接池idleTimeout小于数据库空闲回收时间连接池maxLifetime小于中间层/数据库连接超时数据库idle_in_transaction_session_timeout按业务容忍度设置建议5分钟数据库session_timeout兜底回收建议10分钟这里有两点要提醒配置改动要评估对现有业务的影响不要追求绝对的“零空闲连接”连接池本身需要维持一定的活跃连接来应对突发流量。5.3 监控告警和快速止血连接数问题必须做监控而且要按状态分开监控不能只看一个总数。建议关注这几个指标总连接数 / max_connections 百分比超过80%预警90%告警。idle会话数占比超过50%需要关注。idle in transaction数量持续大于0就要告警。新建连接速率异常突增时提示可能存在连接泄漏。最朴素的监控脚本可以直接用gsql定期跑SELECT count(*) AS total_conn, count(*) FILTER (WHERE state idle) AS idle_conn, count(*) FILTER (WHERE state idle in transaction) AS idle_in_tx, max_connections FROM pg_stat_activity, (SELECT setting AS max_connections FROM pg_settings WHERE name max_connections) AS mc WHERE backend_type client backend;生产环境建议接入统一监控平台但这个SQL截出来的几个指标已经足够做日常巡检。真到连接数打满需要止血时动作优先级这样排用SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE ...清理确认空转的无用会话。内存余量允许时临时调大max_connections撑过高峰期。快速检查应用发布记录回滚疑似引入连接泄漏的版本。根因确认后再改正式配置不能只停留在止血。提示执行pg_terminate_backend前务必确认会话身份和状态不要误杀正在跑业务的长事务连接。6. 排障复盘这次问题为什么能躲过所有常规检查6.1 三条线索叠加才造成“看着很闲却连不上”我们这次最后定位下来是三个因素叠加的结果应用连接池的maximumPoolSize配得过大没有按轻量化版本的max_connections做预算。应用代码里有少量连接泄漏特定操作触发后连接数会出现锯齿状上涨。数据库侧的空闲回收参数虽然配置了但没有真正生效原因是连接池的心跳机制一直维持着连接活性。单独看任何一条都不致命三条叠在一起连接数就在业务量不高的时候慢慢爬到了顶。这也能解释为什么常规检查全都没发现问题CPU不高、内存不高、慢SQL没有只有连接数这个维度在悄悄逼近极限。6.2 轻量化版本上线前连接管理要做一次体检给准备使用GaussDB轻量化版本25.1.30 / 内核505.2.1这类的团队提几个实操建议上线前的压测里专门做一次“长连接保持”测试看连接池空闲连接是否会被正确回收。把所有应用的连接池参数汇总到一张表里由DBA统一审核避免每个项目各自为政。轻量化版本资源有限连接数宁可配紧一点也不要让内存预算和连接数互相打架。内核版本升级后重新检查一遍默认参数不要沿用旧环境的配置。最后再说一个我个人体会最深的地方这次问题最困难的不是修复而是“敢相信连接数真的会打满”。CPU和内存都正常的时候人很容易反复去查SQL、查锁、查磁盘完全没意识到连接管理是一个独立的资源维度。经历过一次之后我现在看任何数据库告警都会先花五分钟看连接数的水位和状态分布再决定要不要深入慢SQL和锁的排查。这个习惯帮我省了很多排查时间也分享给同样被“使用率不高但连不上”折腾过的朋友。