资讯动态

PostgreSQL “too many clients“ 错误全解析:应急处理与根治方案

发布时间:2026/9/17 12:18:26 来源:尧图企业网站定制
1. 错误全貌先搞清楚“太多的客户”到底在说什么做后端开发或者运维的朋友应该都对 PostgreSQL 的报错不陌生。在众多错误里有一类特别“劝退”新人就是标题里写的这句话FATAL: sorry, too many clients already中文环境里常常显示成“致命错误: 对不起, 已经有太多的客户”。第一次遇到这个错误很多人会懵一下。“客户”是什么我明明是连数据库怎么会有太多客户其实这里的“客户”不是业务客户而是client的直译指的是客户端连接。换句话说这句话翻译成人话就是PostgreSQL 服务器现在接待不过来了你已经超过了它允许的最大连接数所以这次连接请求直接被拒之门外。我最早被这个错误折腾是在一个不算大的业务系统里日活不高数据库压力也不大但每到上午高峰时段应用日志里就开始刷这个报错。一开始我还以为是数据库挂了重启之后好一阵过一阵又复发。后来才意识到这不是数据库“坏掉”了而是连接管理出了问题——应用创建连接的速度太快释放连接的速度太慢把 PostgreSQL 的连接槽位占满了。这个错误之所以经典是因为它不是偶发的小概率问题。几乎每一个跑 PostgreSQL 的业务系统只要并发上来、连接池配置不当、或者代码里有连接泄漏都有可能在某个时刻撞上它。而且这个错误一旦出现往往会引发连锁反应新的连接进不来已有的连接可能还在执行慢查询应用端的请求排队超时用户感受到的就是“系统变慢、接口报错”严重的甚至会把整个服务拖垮。所以这篇文章我想从实战角度把这个错误彻底讲透。内容包括这个错误的底层机制是什么、常见的诱因有哪些、上线后怎么快速定位是谁占满了连接、以及从应急到治本的完整解决方案。无论你是刚接触 PostgreSQL 的新人还是已经踩过这个坑想彻底解决的老手这篇文章应该都能给你一些可落地的参考。提示PostgreSQL 中每个客户端连接在服务器端都会对应一个独立的后端进程backend process而不是像某些数据库那样只靠轻量级线程来管理。这是理解“连接数有限”的关键前提后面我会详细讲。2. 为什么会这样连接上限背后的资源逻辑2.1 max_connections 不是随便设的参数PostgreSQL 的默认最大连接数是 100这个值由服务端的配置参数max_connections控制。你可以通过下面的 SQL 查看当前实例的设置SHOW max_connections;在绝大多数默认安装的 PostgreSQL 实例里你会看到结果是100。100 听起来不少但请注意这个数字是整个数据库实例cluster级别的不是单库级别的。也就是说不管你这台机器上有多少个业务数据库所有客户端连接加起来总数不能超过这个数。那为什么 PostgreSQL 要限制连接数呢因为每一个客户端连接在服务端都要独立 fork 一个进程来处理。每个进程都有自己的内存上下文、缓存、临时数据结构。虽然进程之间共享共享缓冲区shared_buffers但每个进程的私有内存开销依然存在。粗算一下一个空闲的连接也可能占用好几 MB 的内存如果连接都在执行复杂查询内存占用会更高。假设max_connections设成 1000每个连接按 5MB 算光连接进程本身就可能吃掉 5GB 内存这还不包括查询执行时的额外开销。所以max_connections表面看是一个连接数限制本质上是一个资源保护机制。它避免客户端把服务器的 CPU、内存、文件句柄耗尽。把它调得过大相当于拆掉了数据库的安全护栏这是我们在调参时必须清醒认识到的一点。2.2 连接是怎么被占满的几个典型场景结合我自己的排查经验连接数被占满通常逃不出下面几类原因。第一类是连接泄漏。这是最常见、也最隐蔽的问题。代码里获取了数据库连接使用完之后没有正确关闭。在 Java 的 JDBC 场景里可能是有连接忘了 close在 Python 的 psycopg2 里可能是异常分支里连接没释放在 Node.js 的 pg 库中可能是回调或异步流程里连接没归还给池。每一个泄漏的连接都会永久占着一个连接槽位直到应用进程重启。日积月累连接数肯定会突破上限。第二类是并发数超过预期。业务量涨了或者某个时间点出现大量请求比如秒杀、定时任务集中触发连接池里的连接全部被占用而且连接池还在继续尝试创建新连接结果新连接创建失败报的正是这个too many clients。第三类是连接池配置错误。这里有两种典型情况一种是应用连接池的maximum-pool-size设得太大多个应用实例每个都开一两百个连接加起来轻松超过数据库的max_connections另一种是连接池的空闲回收时间太长连接长期不被释放而数据库侧又设置了比较短的空闲超时两边策略不一致导致大量连接处于半开状态。第四类是外部工具或后台任务占用。比如运维人员用 DataGrip、pgAdmin 之类的图形工具开了多个连接监控脚本每秒钟跑一次查询定时备份任务等等。单个工具不起眼但架不住数量多。2.3 为什么会有这种“先占满再报错”的体验PostgreSQL 检查连接数的时机是在接受新的连接请求、准备 fork 后端进程的时候。也就是说只有当你发出新连接请求时服务器才会去数一数当前连接数是否已经达到max_connections。如果已经满了它不会排队等待而是直接返回FATAL错误。这一点和某些中间件的行为不一样——它不是“等一会儿再让你连”而是“现在就不让你连”。所以你会看到一种很有意思的现象数据库实例本身可能还活着CPU 占用率也不高已有的连接还能正常执行查询但新的连接就是进不来。这让很多人一开始把问题想复杂了以为数据库出了什么大故障其实逻辑非常简单粗暴槽位满了拒客。理解了这一点我们才能明白为什么解决这个问题的核心思路始终是两条线并行一是把已经被占用的槽位释放出来应急二是让未来的连接数量处在一个可控、合理的范围内治本。3. 定位问题上线后如何快速确认是谁占满了连接3.1 第一件事数一数当前连接数遇到这个错误别急着重启数据库也别急着改max_connections。第一步永远是先看现状。登录 PostgreSQL执行SELECT count(*) FROM pg_stat_activity;这个视图pg_stat_activity是排查连接问题的“第一现场”。它会列出当前所有后端进程的信息包括进程 ID、数据库名、用户名、客户端地址、连接状态、正在执行的 SQL 等。如果这个查询能跑出来而且结果接近max_connections那基本上就坐实了连接耗尽。注意如果你连这个查询都跑不进去说明你的连接尝试也被拒绝了这时候可以尝试通过 unix socket 以超级用户身份连接比如psql -U postgres或者临时增加几个连接槽位再查。3.2 按维度拆解找到“罪魁祸首”数清楚总量之后要回答的问题是到底是谁占用了这么多连接我习惯按几个维度分别查。按连接状态看SELECT state, count(*) FROM pg_stat_activity GROUP BY state ORDER BY count(*) DESC;state字段常见取值有active正在执行查询这是正常的工作状态。idle连接空闲没有在执行任何查询。idle in transaction处于事务中但当前没有执行语句。这个状态要特别警惕说明应用开启了事务但一直没有提交或回滚。idle in transaction (aborted)事务中出现了错误但应用还没回滚。fastpath function call比较少见的调用状态。大量idle连接说明连接池里有很多空闲连接待回收或者存在连接泄漏。大量idle in transaction说明应用层事务管理出了问题连接被事务“挂住”了。按客户端地址和用户名看SELECT client_addr, usename, count(*) FROM pg_stat_activity GROUP BY client_addr, usename ORDER BY count(*) DESC;这一步能快速定位到是哪台应用服务器、哪个数据库用户占用了大量连接。如果发现某个 IP 的连接数特别高那大概率就是那台机器上的应用在“制造”连接。如果多个应用连接数都不低那就是总量规划的问题。按数据库和 wait_event 看SELECT datname, wait_event_type, wait_event, count(*) FROM pg_stat_activity GROUP BY datname, wait_event_type, wait_event ORDER BY count(*) DESC;wait_event能告诉你这些连接在等待什么。比如ClientRead表示连接在等待客户端发送请求空闲状态常见Lock表示在等待锁。如果大量连接阻塞在Lock上问题可能不是连接数不够而是有长事务占着锁不释放导致其他连接排队堆积。3.3 顺着 PID 找到具体的 SQL如果你想看某一条具体连接在执行什么可以直接查SELECT pid, usename, application_name, client_addr, state, query_start, xact_start, left(query, 120) AS query_preview FROM pg_stat_activity ORDER BY query_start DESC;这个查询能把当前所有连接的进程 ID、开始时间、执行的 SQL 都列出来。重点看那些query_start很久之前就开始、但到现在还没结束的查询它们很可能是长事务或慢查询的源头。另外提一个小技巧如果在连接完全打满、你连进数据库都很困难的时候可以在 PostgreSQL 配置文件postgresql.conf里临时调大max_connections然后pg_ctl reload重载配置注意max_connections是需要重启实例的参数不是 reload 就能生效的这一点必须注意。如果连重启都做不到那就只能通过已有的超级用户会话手动终止一些空闲连接来腾位置。提示pg_ctl reload只对SIGHUP级参数生效像max_connections属于postmaster级参数修改后必须重启 PostgreSQL 实例才能真正生效。线上环境重启要评估影响所以更常见的应急手法是直接终止空闲会话。3.4 日志与监控别只盯着眼前pg_stat_activity只能看到当前瞬间的状态如果想回溯连接数是什么时候开始涨的、当时有没有异常那就要靠日志和监控了。PostgreSQL 的日志默认会记录连接建立和断开的信息吗不一定。默认配置下log_connections和log_disconnections通常是关闭的。如果你希望以后能排查连接问题建议在postgresql.conf里开启这两个参数log_connections on log_disconnections on这两个参数对性能影响很小但能让你在事后回看日志时清楚地知道哪个时间点、哪个客户端 IP、以什么用户名建立了连接、连接持续了多久。对于定位“谁在反复创建连接”非常有帮助。另外如果公司有监控系统比如 Prometheus postgres_exporter或者云数据库自带的监控一定要关注pg_stat_activity里的连接数曲线。我看到过不少案例连接数其实是缓慢爬升的早上的低峰期只有 20到晚上高峰期慢慢涨到 100这说明是累积型的问题多半是泄漏另一种是平时只有 30到某个时间点瞬间冲到 100这种情况更像是突发并发或者定时任务。4. 解决方案从应急到治本的分层思路4.1 应急先把火扑灭如果线上已经在报错了当务之急是让服务恢复腾出连接槽位。最直接的手段是终止空闲连接。在 PostgreSQL 里可以用pg_terminate_backend()函数结束指定 PID 的后端进程。比如结束所有空闲超过 10 分钟且不是当前会话的连接SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state idle AND pid pg_backend_pid() AND now() - state_change interval 10 minutes;注意这里我加了一个条件pid pg_backend_pid()目的是避免把自己当前这个查询连接给杀了。如果误杀了当前连接你会看到会话直接断开这是新手经常犯的错误。如果你是超级用户还可以更暴力一点把某个数据库的所有连接都清掉这在做数据库切换或维护时很有用SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname your_database_name;不过要提醒一句pg_terminate_backend()会立刻终止目标进程如果那个连接正在执行重要事务事务会回滚。所以这个操作只适合紧急情况别把它当成日常清理手段。如果你的应用连接池本身有重试机制终止连接后应用会自动重新建立连接影响是可控的如果应用没有重试机制那被终止的连接上正在执行的请求会直接报错。另一个应急手段是修改连接池配置让应用先缩回来。比如你用的是 HikariCP可以临时调小maximum-pool-size重启应用让连接数降下来。但这属于“挥刀自宫”只是暂时治标。4.2 治本第一步应用层做好连接的生命周期管理应急处理完之后必须找到根因。我强烈建议优先检查代码里的连接获取和释放。在 Python 的 psycopg2 中如果一个连接是用psycopg2.connect()手动创建的那么用完必须close()。正确的写法是使用上下文管理器或 try/finallyimport psycopg2 try: conn psycopg2.connect(hostlocalhost dbnametest userpostgres) with conn.cursor() as cur: cur.execute(SELECT 1) except Exception: raise finally: conn.close()但说实话在业务代码里这么手动管理连接很容易在异常分支上漏掉close()。所以我的建议是能上连接池就上连接池不要让业务代码直接创建裸连接。在 Python 生态里SQLAlchemy 的create_engine自带连接池Django 也有CONN_MAX_AGE来控制连接复用。Java 生态里 HikariCP、DruidNode.js 生态里pg库的PoolGo 生态里database/sql的默认连接池都有完善的连接管理机制。关键是别绕过连接池直接 new 连接也别把连接池的 max 值设成无限制或者特别大。4.3 治本第二步把连接池参数设对的思路很多人问连接池的maximum-pool-size到底该设多少这个问题没有标准答案但我可以给你一个业界广泛接受的经验思路。PostgreSQL 官方文档和许多资深 DBA 都推荐过一个简单公式来自 PostgreSQL wiki 里的“连接池”建议连接数 ((核心数 * 2) 磁盘数)比如你的数据库服务器是 4 核 CPU单块 SSD那推荐的连接数大约是4*2 1 9。注意这个公式针对的是数据库服务器的 CPU 核心数不是应用服务器的。它背后的逻辑是PostgreSQL 每个连接都是独立进程进程一多CPU 上下文切换成本会急剧上升。如果连接数比 CPU 核心数的两倍还多大量的 CPU 时间其实都花在了调度和切换上而不是真正执行 SQL。所以如果你是中小型应用maximum-pool-size设置在 10~30 之间是很合理的范围。如果连接池配置是 100而数据库max_connections还保持默认的 100那一个应用实例就能把数据库打满任何其他应用和运维工具都连不进去了。可能会有人担心连接数设这么少并发请求多了怎么办答案是请求可以排队但连接不能滥用。在应用层的连接池里请求会等待获取连接而不是直接让数据库承受压力。这个等待时间虽然会增加响应耗时但比起数据库直接拒绝连接、业务雪崩要好得多。用户能容忍几十毫秒的排队等待但不能容忍“服务挂了”。4.4 治本第三步引入 PgBouncer 或应用侧连接池如果业务并发确实很高而且数据库和应用实例都不止一个那么强烈建议引入PgBouncer这类数据库连接池中间件。PgBouncer 的工作原理是应用连接 PgBouncerPgBouncer 再连接 PostgreSQL。PgBouncer 支持三种连接池模式session模式一个客户端连接在整个会话期间对应一个真实数据库连接。相当于“转接”没有明显的连接复用。transaction模式客户端在事务内才占用真实数据库连接事务结束后立即归还给池。这是最常用的模式能大幅降低数据库侧的真实连接数。statement模式每条语句执行完就归还连接复用效率最高但很多特性如会话变量、临时表、lastval()会失效使用范围有限。对于大多数 Web 应用transaction模式是最合适的。假设你有 100 个应用连接同时访问 PgBouncer但 PgBouncer 可能只需要维持 20 个到 PostgreSQL 的真实连接就能满足这些应用的事务请求。这样一来数据库的max_connections压力瞬间减小很多。配置 PgBouncer 也很简单核心配置文件示例[databases] mydb host127.0.0.1 port5432 dbnamemydb [pgbouncer] listen_addr 0.0.0.0 listen_port 6432 auth_type md5 auth_file /etc/pgbouncer/userlist.txt pool_mode transaction max_client_conn 1000 default_pool_size 20这里default_pool_size是 PgBouncer 为每个数据库保持的真实连接上限。20 个真实连接对于很多中小型业务来说已经非常充足。应用层只需要把数据库连接地址从原来的localhost:5432改成localhost:6432即可也就是指向 PgBouncer应用层的连接池照常工作但真正打到 PostgreSQL 的连接数已经被 PgBouncer 管控住了。注意使用 PgBouncer 后应用层连接池的maximum-pool-size如果设置得明显大于 PgBouncer 的default_pool_size那么应用层的连接大部分时间会处于“等待 PgBouncer 分配连接”的状态。这其实是正常的不必惊慌。判断连接池是否健康的指标应该是数据库侧的活跃连接数和 CPU 利用率而不是应用侧连接池里有多少空闲连接。4.5 治本第四步调整数据库侧参数数据库侧的max_connections当然也可以调大但一定要明白这意味着什么。假设你把它从 100 调到 1000那就意味着你默许最多有 1000 个后端进程存在。每增加一个连接至少多出几 MB 的进程私有内存。如果你的服务器内存不够操作系统可能开始使用 swap反而导致整体性能下降。一个相对稳妥的操作思路是先根据服务器内存估算安全的连接数上限再结合连接池/PgBouncer 的实际需求来设置。比如一台 16GB 内存的服务器操作系统和 PostgreSQL 共享缓冲区各占一部分预留出 3GB 给连接进程按每个连接 5MB 算撑死能支持 600 个连接。设成 300 比较稳妥。但这个“每个连接 5MB”只是粗估真实的每个连接内存开销受配置影响很大。另外max_connections一旦调大还需要检查两个参数shared_buffers共享缓冲区的大小与连接数不直接相关但会占用内存。max_prepared_transactions如果大于 0需要额外分配内存。通常建议保持为 0除非你确实用了两阶段提交。所以我的建议是能不上调就不上调把连接数控制住比扩容连接数更健康。数据库的并发能力靠的是连接池和合理的 SQL 执行计划而不是一味增加连接数。4.6 治本第五步监控与限额配置最后谈谈怎么避免再次踩坑。除了代码层面和连接池层面别忘了加监控告警。我最常用的做法是在监控系统里配置连接数使用率阈值比如达到max_connections的 70% 就告警达到 90% 就紧急告警。这样在连接还没耗尽之前你就能收到通知提前介入处理。如果用的是云数据库比如 RDS 或云厂商的 PostgreSQL通常都有控制台监控已经帮你展示了连接数曲线直接配置告警即可。如果是自建库推荐用pg_stat_activity配合定时采集脚本或者直接上 Prometheus 生态。连接数这种指标非常好采不需要太复杂SELECT count(*) FROM pg_stat_activity;就这一条查询每 30 秒跑一次就能掌握全局连接数的变化趋势。另外还有一个容易被忽略的参数idle_session_timeout。这个参数在 PostgreSQL 14 及以后版本可用可以自动断开空闲超过指定时间的会话。配置示例idle_session_timeout 10min这个设置对长期空闲的连接非常有效。有些应用因为各种原因没能正常归还连接这个参数能在数据库侧做一个兜底清理避免连接被无限期占用。5. 实操过程一次典型故障的完整排查实录5.1 故障背景与现场说一个我实际经历过的案例。一个电商业务系统应用是 Java Spring Boot连接池用的 HikariCP数据库是 PostgreSQL 13部署在 8 核 16GB 的云服务器上max_connections是默认的 100。应用部署了 3 个实例每个实例 HikariCP 的maximum-pool-size初始配置是 50。这个配置看起来“没问题”3 个实例 * 50 150 个潜在连接数据库 100 个连接上限。所以高峰期必然报错。当时线上表现是每天 10 点半左右开始报too many clients already应用日志里大量连接获取失败部分接口 5xx。5.2 排查过程与关键命令我登录数据库后首先执行SELECT count(*), state FROM pg_stat_activity GROUP BY state;结果如下active35idle63idle in transaction2总连接数 100已经打满了。这里一个明显的问题是idle连接占了 63 个说明连接池里的大量连接创建后处于空闲状态但它们依然占着数据库的槽位。接着查每个应用实例的连接数SELECT client_addr, count(*) FROM pg_stat_activity GROUP BY client_addr ORDER BY count(*) DESC;结果三个应用 IP 分别有 40、33、27 个连接。也就是说每个应用实例的连接池都建立了几十个连接加起来超过了数据库上限。问题的根源非常清晰连接池总大小超过了数据库 max_connections。再看 HikariCP 的配置maximum-pool-size是 50minimum-idle默认等于maximum-pool-size也就是说应用启动后就会立即创建 50 个连接并保持空闲。3 个实例就是 150 个连接数据库只有 100 个所以必然失败。5.3 应急处理与正式调整应急处理时我先终止了一批明显空闲超过 30 分钟且没有在执行任何语句的连接给数据库腾出一些槽位SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state idle AND state_change now() - interval 30 minutes AND pid pg_backend_pid();执行之后连接数从 100 降到了 40 多应用恢复。但我知道这只是权宜之计根本没有解决问题。接着我做了三件事第一把每个应用实例的 HikariCP 配置改成spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 idle-timeout: 30000 connection-timeout: 30003 个实例共 60 个连接远低于数据库 100 的上限留出充足余量给运维和其他工具。第二给数据库设置了告警连接数超过 70 就通知。第三复盘为什么最初会配置成 50。原因是开发同学参照了网上“并发量大”的建议但忽略了数据库实际的max_connections。这个案例很典型连接池上限不是拍脑袋定的也不是越大越好它要依据数据库侧的上限和应用侧的实际并发需求共同决定。调整之后系统稳定运行了几个月再也没有出现too many clients already。而且因为从 150 个连接降到 60 个数据库的 CPU 占用率反而还下降了一些慢查询比例也有所降低。原因不难理解连接少了上下文切换少了数据库资源更集中在执行查询上。5.4 从这次故障里提炼的经验这次故障给我最大的感触是绝大多数的“连接数被打满”问题不是数据库不行而是应用侧连接池配置不当。另外一点一定要记住当连接池大小和max_connections之间存在巨大的数量级差距时靠事后清理连接根本解决不了问题。因为连接池会自动补满空闲连接你把数据库侧的连接杀了它马上又会新建。只有从源头把连接池上限压下来才能釜底抽薪。6. 常见问题排查与避坑要点速查为了便于大家在工单里快速参照我把这个错误相关的常见问题和对应的排查方向整理成了一张速查表。现象可能原因优先排查方向偶尔报too many clients一段时间后自动恢复短时间并发突增查 pg_stat_activity看峰值时连接来自哪个IP每天固定时间点报错定时任务集中启动查定时任务脚本调整执行时间或优化任务连接复用报错的同时数据库 CPU 低、内存充足连接被空闲占用按 state 分组统计重点消灭 idle 连接报错后应用重启就好了代码里连接泄漏检查所有获取连接的入口确认 finally/close 是否完备多个应用实例都报错连接池总大小超标算一下各实例连接池上限之和对比 max_connections调整 max_connections 后仍报错参数未重启未生效确认 postgresql.conf 修改后是否重启了实例生产环境无法连接数据库连接数已满自己也被拒之门外用 unix socket 连接并临时终止空闲会话下面再说几个避坑注意事项都是实操中容易踩的点。第一pg_terminate_backend()不要乱杀 active 连接。有些新手一上来就把所有非当前连接的进程都杀了结果直接把正在跑的关键任务打断了。要杀优先杀idle和idle in transaction尤其是idle in transaction (aborted)这种连接不仅占槽位还可能导致事务快照无法释放进而导致数据库膨胀属于必须清理的对象。第二注意idle in transaction和idle的区别。idle只是没有干活但随时可以接新任务idle in transaction则意味着这个连接夹着一个未结束的事务长时间挂在那里的话事务可能会持有锁、占用资源。如果大量连接都是idle in transaction那通常不是连接池的问题而是业务代码里事务边界没控制好。第三修改max_connections后别忘了一起检查操作系统层面的限制。比如ulimit -n文件描述符限制和内核参数fs.file-max。每个 PostgreSQL 连接都要占用一个 socket 文件描述符如果系统级文件描述符限制比max_connections还低那即使你把max_connections调上去了数据库也照样会报“无法创建新连接”之类的错误。第四应用连接池的connection-timeout别设太长。举个例子HikariCP 的connection-timeout默认是 30 秒意思是如果获取不到连接会等 30 秒才报错。如果数据库连接已经满了而应用池把所有请求都卡在等待连接上那么前端请求会全部超时表现为整个服务“假死”。我把connection-timeout设置为 3 秒宁可让请求快速失败也不能让用户无限等待。第五开了 PgBouncer 之后pg_stat_activity 里显示的客户端 IP 就不准了。因为真实连接是从 PgBouncer 建立的client_addr会显示成 PgBouncer 所在机器的 IP。这时候要看真实客户端信息需要看 PgBouncer 自己的日志或者看application_name是否携带了应用信息。如果你早期就规划要用 PgBouncer建议在应用侧给连接设置application_name这样方便区分来源。关于第六个问题我想单独拎出来说清楚很多人在max_connections上误判了负载方向和资源瓶颈的核心。调大连接数只是给问题续命不是解决问题。真正的解法是把连接上的压力转移到连接池和 SQL 优化上。连接数降下来了数据库才有余力去处理更多真正需要 CPU 的活。7. 工具选型与配置参考把我的默认配置给你最后分享一套我目前用得比较顺手的组合也算是一个参考模板。如果是单机中小型业务应用直接用连接池连 PostgreSQL不需要上 PgBouncer但要严格遵守下面的配置原则配置项推荐值/原则PostgreSQL max_connections100~200 之间根据服务器内存和业务量评估应用连接池最大连接数数据库 max_connections 的 30%~50%多个实例要均分预算连接池最小空闲连接数建议比最大连接数小比如最大 20最小 5空闲连接超时30 秒到 5 分钟避免连接长时间占坑连接获取超时建议 3 秒以内避免客户端无限等待每个连接的内存预留按 2~5MB 粗估预留充足内存余量如果是多实例部署或者业务并发较高、数据库实例本身需要保护的场景建议在应用和 PostgreSQL 之间加一层 PgBouncer应用侧连接池配置为 50~100连接 PgBouncer。PgBouncer 的default_pool_size配置为 20~30连接 PostgreSQL。PostgreSQL 的max_connections设置为 100~200足以支撑 PgBouncer 的连接数同时留出运维连接余量。这里面的核心思想是把连接数压力分层消化。应用层可以有很多应用连接但真正打到数据库的连接被 PgBouncer 限制在一个很小的范围内。这样数据库的负载由 PgBouncer 统一调度不会再被几十个应用实例的“连接风暴”打崩。另外再推荐两个常用的排查 SQL我把它放在这儿方便直接抄查看当前连接数使用率SELECT count(*) AS current_connections, current_setting(max_connections)::int AS max_connections, round(count(*) * 100.0 / current_setting(max_connections)::int, 2) AS usage_percent FROM pg_stat_activity;查看每个应用连接的活跃程度SELECT application_name, client_addr, state, count(*) FROM pg_stat_activity GROUP BY application_name, client_addr, state ORDER BY count(*) DESC;第二条在 PgBouncer 后面也很有用因为它显示的是 PgBouncer 建立的连接的状态分布。8. 最后想说的几句大实话PostgreSQL 的sorry, too many clients already是我见过最“直白”的数据库错误之一——它不会给你太多修饰就是一个明明白白的拒绝。可恰恰是这种直白让很多人在第一次遇到时慌了神走了一堆弯路。我自己的经验是遇到这个错误第一步永远不要想着“扛过去”而是要思考“为什么连接会这么多”。从pg_stat_activity看状态、看来源、看时间配合日志和监控基本上十几分钟就能定位到是池子配大了、代码泄漏了、还是并发确实太高。对应的解法其实也就那几板斧压连接池、上 PgBouncer、杀空闲连接、加监控告警。最后再分享一个小技巧在维护窗口去做变更的时候提前把连接池的minimum-idle调成 0让应用在变更期间不要主动维护空闲连接等变更完成后再恢复。这样能显著减少数据库实例重启或主备切换时的连接重建压力。这个细节很多连接池的默认文档不会特意提醒但在生产环境里特别实用。希望这篇内容能帮你在下次面对too many clients already的时候少走几步弯路。如果按照上面的方法排查完依然频繁出现连接打满的情况那就要换个思路去审视业务架构了——是不是查询本身太重、慢查询占据了大量连接资源这个方向看起来离连接数问题很远但往往才是压死骆驼的最后一根稻草。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价