ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

GaussDB连接数打满但负载不高?从参数到连接池的排查治理

GaussDB连接数打满但负载不高?从参数到连接池的排查治理 先说一个比较反常识的结论数据库“使用率不高”和“连接数打满”这两件事是可以同时发生的。GaussDB轻量化版本包版本25.1.30内核505.2.1上我处理过好几次这类问题现象高度一致CPU、内存、磁盘IO都在低位业务却报连不上库应用日志里全是连接超时、连接被拒绝。排到最后问题基本都出在连接数上而且打满连接的那些会话往往不在跑业务只是“占着坑不干活”。这篇文章就围绕这个故障把排查思路、参数原理、应急恢复手段完整写一遍。这套排查思路适合刚接手GaussDB的DBA、从MySQL迁移过来的运维同学以及被连接数告警折磨过的应用负责人。故障本身不难难的是别被“使用率不高”带偏方向。1. 先分清“使用率不高”和“连接数打满”之间的逻辑关系1.1 轻量化版本的默认连接水位比很多人想的要低GaussDB轻量化版本最常见的部署场景是单机、小规格我见过不少环境是4C8G、8C16G。这类版本的内核能力和标准版基本一致但默认参数是按“够用、别把内存吃满”的思路来的max_connections这个关键参数往往设置得并不高。标准版部署可能到1000、2000轻量化版本默认可能只有几百具体看安装模板和实际版本。很多团队是从MySQL迁过来的在MySQL那边习惯把max_connections调到1000、2000顺手把应用连接池也调得很大。迁移到GaussDB轻量化版本之后应用侧连接池没有做减法数据库侧参数也没跟着核第一次流量稍微上来一点连接数就直接顶穿。这里有个非常容易踩的坑连接数打满之后新连接会直接被拒绝报错类似too many clients already。但这个报错和数据库“资源耗尽”没有直接关系它只说明“会话槽位满了”。如果你只盯着CPU和内存看会发现在连接打满的同一时刻数据库各项指标都正常得离谱。1.2 “使用率不高”恰恰是“连接打满”的烟雾弹为什么这个故障特别迷惑因为常规思维是“资源没满说明数据库没压力没压力怎么会连不上”。但连接数和负载是两个完全不同的维度。CPU使用率高通常对应的是SQL在“跑”、在消耗计算资源连接数满只能说明“会话槽位被占满了”至于这些会话是在跑SQL还是在闲等连接数本身看不出来。我处理过的案例里最夸张的一次是pg_stat_activity里几百个连接绝大多数状态是idle或者idle in transaction也就是每个连接都活着、都占着位置但什么都不干。这种状态下数据库使用率当然不高可新连接就是进不来。所以拿到这类报障第一反应不应该是一头扎进慢SQL、锁等待里而是先问一句连接数现在有多少状态分布怎么样排查顺序错了后面全是无用功。2. 一步步定位先把“谁占了连接”找出来2.1 连接打满后用什么方式能连进数据库这里有个很现实的问题如果数据库完全无法建立新连接排查连进都进不去。所以先说“后门”。openGauss系保留了一个给管理员用的连接预留参数在GaussDB文档里通常叫reserved_connections不同发行版命名可能略有差异以现场版本官方文档为准。这个参数默认预留少量连接给高权限用户登录使用。很多生产环境里这个参数被当成“浪费连接数”调成了0我强烈不建议这么做它是故障时最后一道闸门。实际连接打满时我用下面的方式挤进去gsql -d postgres -U omm -h /tmp -p 5432注意-h指向的是本地Unix Socket目录不是远程IP。很多连接打满的场景下TCP连接已经被占完但本地Socket走的是系统进程间通信通常还能登进去。如果连不上就看一下unix_socket_directory参数指向哪里把-h换成那个目录。登进去之后第一件事就是看连接数。如果本地Socket也进不去、预留连接也被占完那就只剩两条路一是到操作系统层面处理空闲会话进程二是重启数据库实例。kill进程有硬中断事务的风险临时代价可能比重启还大这一步要放到最后再考虑后面4.1会细说。2.2 获取连接现状先看总量再看状态能登进去之后第一步看总量。下面这条SQL我每次必跑SELECT current_setting(max_connections)::int AS max_conn, (SELECT count(*) FROM pg_stat_activity) AS used_conn, round((SELECT count(*) FROM pg_stat_activity)::numeric / current_setting(max_connections)::int * 100, 2) AS used_pct;如果used_conn已经到了max_conn结论基本就确定了。接下来把状态分布拉出来SELECT state, usename, count(*) FROM pg_stat_activity GROUP BY state, usename ORDER BY count(*) DESC;我见过的打满场景里最常见的就是idle数量爆炸其次是idle in transaction。idle多绝大多数是应用连接池里“泡”着的空闲连接连接池为了后续请求方便会维持一定数量的空闲连接太多就浪费槽位。idle in transaction多说明有事务开了没提交、没回滚。这种比idle更危险因为它不只占连接还在持锁、持有事务快照后端数据文件都可能受长事务影响必须重点清理。active正在执行SQL通常和“使用率不高”矛盾但也要看执行的什么。后续排查就围绕这几种状态展开。pg_stat_activity里的state_change字段记录了最近一次状态变化的时间判断“什么时候开始卡的”非常有用。2.3 按来源拆解是哪个服务、哪台服务器在占连接确认了状态还要确认“谁的连接”。我用下面这条SQL把来源和应用名聚合出来SELECT client_addr, application_name, usename, state, count(*) FROM pg_stat_activity GROUP BY 1, 2, 3, 4 ORDER BY count(*) DESC;这条SQL能直接回答几个关键问题是单个应用占的还是多个应用叠加占的连接主要来自哪台机器是不是监控系统、备份工具之类的“隐性连接”也掺了一脚有一次我排查了半小时最后发现某个中间件的连接池minimum-idle配了50同时部署了6个实例加上监控探针、数据导出工具合起来刚好把连接数吃满而且所有连接全是idle整个数据库负载曲线平得像一条直线。这就是典型的“多个连接池叠加单看每个都不吓人合在一起刚好超限”。找出来源后能和业务方对上号后面无论是砍连接池、杀会话还是做限流心里就有谱了。2.4 连接池配置是“隐藏的重灾区”GaussDB端的max_connections只是一个上限真正决定“会打来多少连接”的是应用侧连接池的配置。很多团队只改了数据库侧的上限应用侧完全没有联动。先看Java社区最常见的HikariCP配置通常是这样的spring.datasource.hikari.maximum-pool-size50 spring.datasource.hikari.minimum-idle10 spring.datasource.hikari.idle-timeout600000 spring.datasource.hikari.max-lifetime1800000 spring.datasource.hikari.connection-timeout30000再看Druid常见配置是这样的spring.datasource.druid.max-active50 spring.datasource.druid.min-idle10 spring.datasource.druid.max-wait30000 spring.datasource.druid.remove-abandonedtrue spring.datasource.druid.remove-abandoned-timeout180 spring.datasource.druid.log-abandonedtrue这里最容易犯的错是每个服务单独看都不多但服务个数一多“N个服务 × 50连接池上限”就远超数据库max_connections。正确做法是把所有要连库的服务压在一起统计留出20%上下的余量给运维工具、监控探针和后台任务。我自己的统计口径是max_connections建议值不小于“所有应用连接池上限之和 20个左右运维预留”同时不超过“机器内存能支撑的量”。这两个条件如果冲突优先砍连接池而不是继续调大数据库参数。3. 参数调整和原理max_connections不是越大越好3.1 max_connections在轻量化版本上的“内存账”先说一个很容易被忽略的细节max_connections是Postmaster级别参数用gs_guc reload不会生效必须gs_guc set加上gs_ctl restart重启实例。所以每次调大之前一定要先算清楚内存账。GaussDB开启线程池后后端并不是一个连接一个操作系统线程连接与worker线程是解耦的但客户端连接相关的会话上下文、排序缓冲、临时内存仍然要占资源。按我的现场经验一个连接长期占用最低也要2MB到5MB内存如果会话里跑大排序、大临时表占用会高更多。假设机器8GB内存系统和其他进程先吃掉3GB留给数据库的有效内存约5GB如果再开2GB左右的shared_buffers剩下给连接和查询的内存其实很有限。这种情况下max_connections设到2000风险很大——连接数真的被推到高位时每个连接分到的可用资源被严重挤占出现OOM或者实例启动失败都不奇怪。轻量化版本的建议区间我通常这样给机器规格max_connections 建议区间备注4C8G300~500核心优先连接池严控8C16G500~1000比较均衡16C32G1000~2000资源够时才放开这组数据不是官方硬指标是我在不同规格下按“内存账业务并发”反推出来的经验值大家还是要基于现场情况来配别照抄。3.2 会话超时参数让“僵尸会话”自己走人除了调大连接上限更重要的工作是让空闲连接有“自动下线”的机制。两个参数是重点session_timeout会话在整体空闲状态下的最大等待时间超过后数据库主动断开。idle_in_transaction_session_timeout事务中空闲的会话超过指定时间数据库主动回滚并断开。在连接池已经托管空闲连接的情况下这两个参数的设置要谨慎。如果设得太短比如30秒应用侧扩缩容频繁的场景可能频繁断连但完全不设idle in transaction的会话可能挂一整天。我见过一个特别典型的场景应用开启事务后因为一个异常分支没有走commit也没有走rollback事务一直挂着。在代码不好改的前提下是idle_in_transaction_session_timeout帮了大忙——设成60秒这类悬挂事务最多存活一分钟连接立刻释放。别小看这个设置它在“应用代码改不动”的时候是DBA手里为数不多的硬手段。修改方式gs_guc reload -D /gaussdb/data -c session_timeout 600 gs_guc reload -D /gaussdb/data -c idle_in_transaction_session_timeout 60这两个参数比较友好的地方是它们属于reload级别不需要重启数据库在线变更就能生效适合在故障现场直接调。3.3 预留连接和线程池两个容易被忽略的“保险”接着上面提到的reserved_connections在连接打满时有没有预留连接决定了管理员能不能“挤进门”。很多环境里这个参数处在默认值甚至被人误解为浪费连接数而调成0。我的观点是这个参数宁可少不能没有它是故障处理时的最后一道闸门。另外还有线程池开关。GaussDB/openGauss系默认开启线程池参数是enable_thread_pool。开启后大量客户端连接并不会每个都创建独立操作系统线程而是由线程池里的worker处理连接数和OS线程数解耦。这个机制对承载大规模连接池有天然优势但前提是要把线程池参数和应用并发量匹配好否则连接进来了请求却排队表现就是连接没满但“应用等不到数据库响应”。排查时不妨看一眼线程池状态别只盯着连接数。SHOW enable_thread_pool; SHOW reserved_connections;enable_thread_pool这类参数通常也是重启级别我建议非必要不动它和连接数打满不是强相关动错了反而会影响整体并发行为。3.4 版本差异和已知问题别忽略“内核505.2.1”这个变量处理这类故障我一直坚持先看版本号。包版本25.1.30、内核505.2.1这两个号看着不起眼但它们决定了你查哪些文档、问哪些问题。同一套排查逻辑在不同内核版本上的表现可能完全不一样。我有一次排查连接释放慢的案例参数、应用配置、连接池全部看了一遍数据和理论都对得上但问题就是复现。最后发现是某个补丁版本在连接管理上有已知缺陷把补丁打到厂商推荐版本后连接回收恢复正常。所以如果参数排查全部合理、应用侧也都配合改过问题依然复现一定要回头查release notes看这个内核版本有没有连接管理相关的修复必要时直接联系厂商技术支持要一个明确的修复版本或规避手段。4. 应急恢复和长期治理可持续的连接数安全水位4.1 紧急恢复现场三个动作先把业务拉回来故障正在发生时第一步是让业务先恢复而不是开长会分析。我的顺序是第一腾出连接数。优先清理“最没有价值”的连接。通常选择idle状态且空闲超过10分钟的会话以及idle in transaction超过60秒的会话-- 清理空闲事务会话超过60秒 SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state idle in transaction AND now() - state_change interval 60 seconds AND pid pg_backend_pid(); -- 清理空闲会话超过10分钟 SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state idle AND now() - state_change interval 10 minutes AND pid pg_backend_pid();执行之前一定先跑一遍不带pg_terminate_backend的SELECT确认范围避免误杀。这里有一条原则先杀idle再杀idle in transaction最后才考虑activeactive里如果是核心业务的关键SQL贸然杀掉可能造成数据回滚或业务报错要业务方确认后再动。第二如果打死都连不上库只能从操作系统层面处理。先看数据库进程列表ps -ef | grep gaussdb从输出里挑出连接时间最久、明显空闲的客户端会话进程逐条处理。不过这属于最后手段能不用就不用能先走SQL清理就先走SQL清理。第三紧急情况下临时调大max_connections给业务“扩容”gs_guc set -D /gaussdb/data -c max_connections 2000 gs_ctl restart -D /gaussdb/data注意最后必须重启这类参数不是reload就生效的。重启前先备份postgresql.conf并确认没有什么正在跑的长事务不能断。临时调大是应急手段不是长期方案等业务恢复后还是要回到“内存账”逻辑算清楚到底合不合理。4.2 长期治理给连接池和数据库立规矩应急是一时的关键是让同样的问题不重复发生。我一般推动业务侧做三件事第一统一连接池配置模板。禁止每个服务各写各的。把“所有服务连接池上限之和不能超过数据库max_connections的80%”写进发布规范上线前由DBA审批。第二设置合理的空闲回收。应用侧连接池设好idle-timeout或min-evictable-idle-time-millis数据库侧配合session_timeout和idle_in_transaction_session_timeout两侧一起把僵尸连接清除。第三建立连接数看板和告警。建议监控SQLSELECT count(*) AS used_conn, current_setting(max_connections)::int AS max_conn FROM pg_stat_activity;脚本里把阈值设在80%报警、90%告急、100%单独告警。告警信息里带上按client_addr分组的TOP5来源接到告警的人一眼就能判断是哪个服务在打连接。4.3 常见问题速查连接数打满排查表现象可能原因快速定位方式处理方法连接数满但CPU/内存低空闲连接堆积、连接池过大按state分组统计杀空闲会话收缩连接池大量idle in transaction应用事务未提交/未回滚where stateidle in transaction应用修代码库侧设超时连接数未满但应用连不上线程池打满或认证/网络问题查看线程池状态、ps、日志按实际场景分头排查重启后瞬间连接数暴涨连接风暴对比应用启动时间和连接创建时间应用侧加预热和限流多个服务连接池叠加超限连接池配置未做总账按client_addr、application_name分组统一连接池配置规范调大max_connections后内存紧张内存账没算监控内存、swap、OOM日志砍连接池别盲目加参数补丁升级后出现连接回收慢版本差异或已知问题查release notes、提工单升级补丁或讨规避方案这张表看着简单实际上我每次排查基本都在这些行里打转很少跳出这个范围。5. 我踩过的坑和最后的三句话心得5.1 一个让我纠结了大半天的案例前阵子遇到一个类似场景业务方反馈“连接数满了”我登上去一看pg_stat_activity里四百多个连接idle in transaction占了三百多个连接来源几乎集中在同一个应用。一开始我怀疑是应用代码事务没关让业务方翻代码翻了大半天结果什么也没查出来。后来发现是应用侧用的Druid连接池没有开remove-abandoned加上数据库端的idle_in_transaction_session_timeout是默认值0不限制两边都没有自动回收机制事务悬挂的会话越积越多最终把连接数吃满。当时只要在库侧把超时参数reload一下再杀掉一批会话业务就能恢复。那次之后我养成了一个习惯无论故障表象是什么先检查两侧有没有自动回收机制没有就先补上再谈其他。还有一个教训是调大max_connections之前一定要先看机器规格。有一次应急时我直接把参数调到了3000结果数据库启动后内存使用率一路飙升差点把系统搞出OOM。后来我给自己定了条规矩在轻量化版本上连接数调整必须和内存核算同步做宁可多花十分钟算清楚不要事后花两小时救火。5.2 最后想说的三句话第一句连接数打满不等于负载高别被“使用率不高”骗了先看pg_stat_activity状态分布。第二句数据库侧参数只能兜底真正管住连接数上限的是应用连接池所有服务连库的连接上限要合起来算总账。第三句预留连接、会话超时、连接数告警这三件套是每个GaussDB轻量化环境都该配齐的基础设施。没有它们下次故障只是时间问题。很多排查工作做到最后拼的不是多高深的技术而是有没有把基础措施做到位。这套思路帮我在多个现场快速止血也希望对你下一次排查有帮助。
返回列表