连接池配置(PgBouncer)
PostgreSQL 的进程模型决定了它对「连接数」非常敏感:每条客户端连接都对应一个独立的后端进程,而不是一个线程。连接开得越多,内存与进程切换开销越大,性能反而下降。因此在生产环境里,「加连接池」几乎和「调 shared_buffers」一样,属于必做项。
本节从进程模型的代价讲起,说明为什么盲目调大 max_connections 是反模式,然后以 PgBouncer 为主线讲清三种池模式的取舍、事务模式下的各种限制,以及 PHP 应用最容易踩的预处理语句(prepared statement)陷阱。最后给出其他池化方案的横向对比与监控建议。
前置知识
阅读本节前,建议先了解:统计信息对执行计划的影响、postgresql.conf 配置
基础概念
为什么 PostgreSQL 需要连接池
PostgreSQL 采用多进程模型:Postmaster 收到连接请求后 fork() 出一个后端进程(backend process),此后这条连接的所有 SQL 都由这个进程处理,直到连接断开。
客户端 A ──┐
客户端 B ──┤ ┌──────────────┐
客户端 C ──┼── 连接 ──▶ │ Postmaster │ ── fork ──▶ 后端进程 A
客户端 D ──┤ └──────────────┘ 后端进程 B
客户端 E ──┘ 后端进程 C
后端进程 D
后端进程 E每个后端进程都要占据:
| 组成 | 典型开销 | 说明 |
|---|---|---|
| 进程本身 | 约 2~5 MB | 页表、栈、可执行代码映射(大半为共享的只读页) |
| PgBackend 私有内存 | 约 1~3 MB | 会话上下文、catalog cache、解析结果缓存 |
work_mem | 按需,每条排序/哈希最多一份 | 单条复杂查询可能同时用到多份 |
temp_buffers | 默认 8MB 上限 | 访问临时表才实际分配 |
一个空闲连接的基础内存开销通常在 5~10 MB 量级。也就是说 1000 条连接光是「挂在池外」,就可能吃掉 5~10 GB 内存——而这部分内存并没有用于缓存数据。
连接不是免费的
很多人把数据库连接当成「HTTP 长连接的廉价变体」。实际上在后端进程模型里,一个连接 = 一个操作系统进程 = 一份独立的内存与调度实体。连接数从 100 涨到 1000,吞吐量往往不涨反跌。
连接建立的真实成本
fork() 一个后端进程远比建立一个线程昂贵。开一条新连接大致要经历:
客户端发起连接
│
├─ 1. TCP 三次握手 + TLS 握手(若启用 SSL) ← 网络往返
├─ 2. Postmaster 鉴权(pg_hba 匹配、SCRAM 计算) ← CPU
├─ 3. fork() 后端进程 + 复制页表(COW) ← 系统调用
├─ 4. 后端初始化:读 catalog、加载配置、建内存上下文 ← 磁盘 I/O + CPU
└─ 5. 完成启动包交换,连接可用在本地 Unix socket 上,这条链路约 5~20 ms;跨网络或启用 TLS、SCRAM 迭代次数较高时,30~100 ms 也很常见。
「短连接 + 高并发」的业务(典型如 PHP-FPM 的每个请求建一条连接)会把大量时间浪费在这条链路上。这正是连接池的核心价值:把连接的建立成本摊薄到多次请求上。
连接数与 CPU 的关系
数据库能并行处理的「活跃」连接数,最终受限于 CPU 核数,而不是 max_connections。
最优并发数 ≈ CPU 核数 × 2 ~ 4
(超线程算物理核数的一半;SSD 优于 HDD 时可取高值)原因在于:连接一旦真正执行 CPU 密集的查询,就必然占用一个核。连接数远超核数时,只会带来:
| 现象 | 原因 |
|---|---|
| 上下文切换(context switch)暴增 | 太多进程争抢少数核 |
| 锁等待、LWLock 争用变多 | 并发事务数上升 |
| 缓存命中率下降 | 每个后端的 catalog cache 等私有内存互相挤占 |
| 尾延迟(p99)恶化 | 队列变长,请求排队时间不可控 |
所以正确的做法是:保持活跃并发数接近 CPU 核数,用连接池把多余请求排队,而不是让它们都变成后端进程。
PostgreSQL 18 没有内置连接池
需要明确一点:PostgreSQL 18 仍然没有内置连接池。官方一直把这件事交给外部组件(PgBouncer、pgpool-II、odyssey)或应用层连接池。
可能出现混淆的是协议层的 max_prepared_statements(PG14 引入)——它不是连接池,只是让服务端支持协议级命名预处理语句,从而让 PgBouncer 在事务模式下也能复用预处理语句。两者不要混为一谈。
语法与用法
PgBouncer 简介
PgBouncer 是最常用的 PostgreSQL 连接池,特点是:
| 特性 | 说明 |
|---|---|
| 单进程、事件驱动 | 基于 libevent,一个进程用较少内存支撑上千客户端 |
| 协议感知 | 讲 PostgreSQL 前端/后端协议,客户端无需改代码(只改端口) |
| 无需特权限 | 不需要超级用户,普通连接即可 |
| 支持 TLS | 客户端到 PgBouncer、PgBouncer 到 PostgreSQL 均可加密 |
支持 auth_query | 可用一条查询动态获取用户密码,避免重复维护名单 |
三种池模式
PgBouncer 通过 pool_mode 决定「服务端连接在多长的时间粒度上被复用」:
session 模式 客户端 ──连接──▶ [独占服务端连接] ──连接──▶ PostgreSQL
连接存续期间服务端连接不释放
transaction 模式 客户端 ──事务──▶ [占用服务端连接] ──提交/回滚──▶ 归还到池
只有事务期间独占,事务间可被别人复用
statement 模式 客户端 ──语句──▶ [占用服务端连接] ──语句结束──▶ 归还到池
每条语句独占(多语句事务不可用)| 模式 | 服务端连接复用粒度 | 并发收益 | 兼容性 | 适用场景 |
|---|---|---|---|---|
session | 整个会话 | 低 | 完全兼容 | 有会话状态需求(SET、临时表、LISTEN) |
transaction | 每个事务 | 高 | 有约束(见下) | 短事务 OLTP,绝大多数场景的推荐值 |
statement | 每条语句 | 最高 | 只支持单语句、无事务 | 极简只读/自动提交场景,较少使用 |
默认值是 session。要获得连接池的主要收益,需要改成 transaction。
关键配置项
PgBouncer 的配置文件(pgbouncer.ini)中最需要理解的几个参数:
| 参数 | 默认值 | 含义 |
|---|---|---|
pool_mode | session | 池模式,OLTP 建议 transaction |
max_client_conn | 100 | 允许的客户端连接总数上限 |
default_pool_size | 20 | 每个 user+database 组合的服务端连接数 |
min_pool_size | 0 | 常驻保底的服务端连接数 |
reserve_pool_size | 0 | 超出 default_pool_size 时额外可借的连接数 |
reserve_pool_timeout | 5 | 等待多久后启用 reserve pool(秒) |
server_idle_timeout | 600 | 空闲服务端连接被关闭的时间(秒) |
server_lifetime | 3600 | 服务端连接的最长存活时间(秒) |
server_connect_timeout | 15 | 建立服务端连接的超时(秒) |
query_wait_timeout | 120 | 客户端在池中等待连接的超时(秒),超时后报错 |
几个关键理解点:
1. max_client_conn 与 default_pool_size 是两层限制
客户端连接(最多 max_client_conn 条)
│ 排队等待
▼
服务端连接(每 user+db 最多 default_pool_size [+ reserve_pool_size] 条)
│
▼
PostgreSQL(后端进程数 ≈ 各池之和)max_client_conn 可以设得很大(如 10000),因为空闲的客户端连接在 PgBouncer 里只是内存中的状态,不占后端进程。真正决定 PostgreSQL 负载的是所有池的 default_pool_size 之和。
2. 服务端连接总数必须低于 max_connections
Σ (每个 user+db 的 default_pool_size + reserve_pool_size)
+ 管理与监控连接
应 < PostgreSQL 的 max_connections - superuser_reserved_connections3. reserve_pool_size 是「临时扩容」而非「常驻容量」
只有当某池的连接全忙、且客户端等待超过 reserve_pool_timeout 时,才从 reserve pool 借连接。用完归还。
详细说明
连接池的架构位置
应用层 数据库层
┌─────────────┐ ┌──────────────────────┐
│ PHP-FPM │──┐ │ PostgreSQL │
│ worker 1..N │ │ max_client_conn │ ┌────────────────┐ │
├─────────────┤ ├──▶ ┌───────────┐ ──┼─▶│ 后端进程 1..M │ │
│ 其他服务 │──┘ │ PgBouncer │ │ └────────────────┘ │
└─────────────┘ │ (transaction)│ │ M = Σ default_pool │
└───────────┘ └──────────────────────┘
池内排队 连接数稳定、可控关键收益:PostgreSQL 侧看到的连接数变少且稳定,max_connections 可以保持在合理范围(如 200~500),内存与上下文切换压力大幅下降。
事务模式下的限制
这是选择 transaction 模式必须理解的代价。事务模式下,服务端连接会在事务边界被回收,因此任何跨越事务的会话状态都会失效。
| 限制 | 原因 | 应对 |
|---|---|---|
会话级 SET 不生效 | 下个事务可能拿到另一条服务端连接 | 用 SET LOCAL(事务级)或连接串参数 |
| 命名预处理语句不可直接复用 | 服务端连接变化后语句不存在 | PG14+ 配合 max_prepared_statements(见下节) |
WITH HOLD 游标不可用 | 游标跨事务存活,本质是会话状态 | 改用普通游标,或把结果集一次取出 |
LISTEN / NOTIFY 不可靠 | 监听绑定在具体服务端连接上 | 用独立连接(session 池或直连) |
| 会话级 advisory lock 有风险 | 锁绑定在会话上,会话被复用后语义混乱 | 用事务级 pg_advisory_xact_lock() |
临时表、PREPARE、SET search_path | 均为会话状态 | 改为事务内使用或应用层维护 |
pg_stat_activity 对应关系变化 | 一个客户端会对应多条服务端连接记录 | 用 application_name 关联 |
典型报错:prepared statement "S_1" does not exist
在 transaction 模式下,PHP PDO / Java JDBC 使用协议级命名预处理语句时,很容易遇到:
ERROR: prepared statement "S_1" already exists
ERROR: prepared statement "S_1" does not exist原因是客户端在连接 A 上 PREPARE,下个事务却被分配到连接 B。解决办法见下一节。
预处理语句与事务池
这是 PgBouncer + transaction 模式最经典的坑,方案有两代演进:
方案一:客户端不用命名预处理语句(旧办法)
让驱动使用「简单查询协议」或匿名预处理语句。PHP PDO 通过 ATTR_EMULATE_PREPARES 控制:
// 推荐用于 PgBouncer + transaction 模式:由 PDO 在客户端做参数转义与拼接
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_EMULATE_PREPARES => true,
]);ATTR_EMULATE_PREPARES | 行为 | 与 PgBouncer 事务模式 |
|---|---|---|
true | PDO 在客户端模拟,把参数值内联进 SQL | 兼容(不发送命名语句) |
false(默认) | 走服务端协议级预处理语句 | 可能报错,除非配合方案二 |
代价是丢掉了服务端预处理语句的「解析一次、执行多次」收益,且需信任驱动的参数转义。
方案二:服务端支持协议级预处理语句复用(PG14+ 推荐)
PostgreSQL 14 引入了 max_prepared_statements(默认 0,即关闭)。它会为每条协议级预处理语句按名称建立缓存,PgBouncer 1.21+ 在 transaction 模式下可以跟踪并重放这些语句,使命名预处理语句在跨服务端连接时依然有效。
-- postgresql.conf 或 ALTER SYSTEM
max_prepared_statements = 200 -- 0 表示禁用;正数表示每会话可缓存的语句数# pgbouncer.ini
pool_mode = transaction
max_prepared_statements = 200 # PgBouncer ≥ 1.21,需与 PG14+ 服务端配合这样就能保留 ATTR_EMULATE_PREPARES => false,同时兼容事务池。
判断该用哪种方案
- PostgreSQL 版本 < 14:只能用方案一(客户端模拟)。
- PostgreSQL ≥ 14 且 PgBouncer ≥ 1.21:优先方案二,兼顾兼容性与性能。
- 无论哪种,都要在生产同配置的测试环境里跑一遍业务 SQL,别只看「连接能建立」。
其他池化方案对比
| 方案 | 类型 | 优点 | 缺点 | 适用 |
|---|---|---|---|---|
| PgBouncer | 独立中间件 | 轻量、稳定、事务模式成熟、无状态依赖 | 事务模式有会话状态限制 | 绝大多数 OLTP,首选 |
| pgpool-II | 独立中间件 | 除连接池外还带读写分离、自动故障转移、负载均衡 | 更重、配置复杂、自身也可能成为瓶颈 | 需要读写分离/故障转移的一体化方案 |
| odyssey | 独立中间件 | Yandex 出品,多线程、性能高、支持路由 | 生态相对小 | 高吞吐、愿意尝试新组件 |
| 应用层连接池 | 进程内 | 无额外中间件、可精细控制 | 每个应用实例各持一批连接,总量难控;PHP-FPM 模型下效果差 | Java(HikariCP)、Go、常驻进程语言 |
| 云托管内置池 | 云服务 | 免运维、与应用一体 | 能力有限(多为事务模式)、绑定厂商 | 使用 RDS/Aurora/Cloud SQL 且无自运维需求 |
各方案的一句话定位:
- PgBouncer:把「连接复用」这件事做到极致,其它什么都不做。
- pgpool-II:连接池只是它的功能之一,主打读写分离与高可用。
- odyssey:PgBouncer 的多线程替代品,追求更高并发。
- 应用层池(HikariCP 等):适合常驻进程(Java/Go);PHP-FPM 每请求一个进程的模型下,进程结束池就没了,收益很有限。
- Swoole 等常驻 PHP:在常驻模式下可以做应用层池,但要配合协程与连接生命周期管理,复杂度不低。
PHP-FPM 场景的常见误区
PHP-FPM 是「每请求一个进程、请求结束进程可能被回收」的模型。用 PHP 代码里的静态变量做「连接池」,在 FPM 下基本无效(请求结束即释放)。PHP 场景要真正池化,要么用 PgBouncer/pgpool-II 这类外部中间件,要么把应用改成 Swoole/Swoole 协程常驻模型再做应用层池。
max_connections 的调整建议
max_connections 决定了后端进程数上限:
| 参数 | 默认值 | 说明 |
|---|---|---|
max_connections | 100 | 所有连接的硬上限(含复制、后台连接) |
superuser_reserved_connections | 3 | 为超级用户保留的连接数,普通用户无法占用 |
reserved_connections | 0 | PG17+ 新增,为特定角色保留(配合 pg_use_reserved_connections) |
调整原则:
1. 先上连接池,把 PostgreSQL 实际连接数压下来
2. 再按「所有池的 default_pool_size 之和 + 复制/管理连接 + 余量」定 max_connections
3. 不要为了「让应用不用改」就把 max_connections 调到 2000、5000superuser_reserved_connections 存在的原因很实际:连接数打满时,普通用户连不进去,但你必须还能用超级用户登录进去「救火」。
# 一个相对保守的 OLTP 配置示例(16 核 / 64GB 机器)
max_connections = 300
superuser_reserved_connections = 3实战示例
场景一:一个典型的 PgBouncer 配置
需求:PostgreSQL 16 核机器,PHP-FPM 有 800 个 worker,要求活跃后端连接数不超过 100。
; /etc/pgbouncer/pgbouncer.ini
[databases]
; 把 appdb 映射到真实 PostgreSQL
appdb = host=10.0.0.10 port=5432 dbname=appdb
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
; —— 池模式 ——
pool_mode = transaction
; —— 客户端侧 ——
max_client_conn = 1000
default_pool_size = 80 ; 每个 user+db 的服务端连接数
reserve_pool_size = 20 ; 突发时可临时多借 20 条
reserve_pool_timeout = 3 ; 等待 3 秒后启用 reserve pool
query_wait_timeout = 30 ; 客户端排队超过 30 秒报错,避免无限等待
; —— 服务端连接生命周期 ——
server_idle_timeout = 300
server_lifetime = 1800
server_connect_timeout = 5
; —— PG14+ 预处理语句支持 ——
max_prepared_statements = 200
; —— 日志与监控 ——
log_connections = 0
log_disconnections = 0
stats_period = 60容量核算:
default_pool_size(80) + reserve_pool_size(20) = 100
max_connections(300) - superuser_reserved_connections(3) = 297
100 << 297 ✅ 有充足余量,且 100 个活跃连接 ≈ 16 核 × 6,仍在合理区间场景二:验证连接池是否生效
1. 在 PostgreSQL 侧确认实际连接数
SELECT usename,
application_name,
state,
count(*) AS conns
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY usename, application_name, state
ORDER BY conns DESC; usename | application_name | state | conns
---------+------------------+---------------------+-------
appuser | pgbouncer | active | 78
appuser | pgbouncer | idle | 19
appuser | psql | idle | 1
(3 rows)只要 application_name 显示为 pgbouncer,且总数远低于应用 worker 数,说明池在工作。
2. 在 PgBouncer 控制台查看池状态
psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncerSHOW POOLS; database | user | cl_active | cl_waiting | sv_active | sv_idle | sv_used | pool_mode
----------+----------+-----------+------------+-----------+---------+---------+-----------
appdb | appuser | 12 | 0 | 80 | 0 | 20 | transaction关键列含义:
| 列 | 含义 | 关注点 |
|---|---|---|
cl_active | 正在执行事务的客户端连接 | 与 sv_active 一起看 |
cl_waiting | 正在排队等连接的客户端 | 长期 > 0 说明池太小 |
sv_active | 正在被使用的服务端连接 | 逼近 default_pool_size 说明接近饱和 |
sv_idle | 空闲的服务端连接 | 长期为 0 且 cl_waiting > 0 要扩池 |
sv_used | 刚被归还、待清理的连接 | 偶发正常 |
3. 判断是否需要扩池
若 cl_waiting 长期 > 0 且 sv_active == default_pool_size:
→ 要么扩 default_pool_size(确认 CPU 有余量)
→ 要么优化 SQL(单个事务太慢,占着连接不放)扩池不是万能解
cl_waiting > 0 时先别急着加 default_pool_size。如果根因是某条慢 SQL 占用连接数秒,扩池只是把「排队」变成「一起变慢」,还可能把 CPU 打满。先看 sv_active 上跑的是什么。
场景三:实测连接建立成本
要判断「到底需不需要池」,最直接的依据是实测连接开销。可以用 pgbench 对比「每次建连接」与「连接复用」:
# 1) 每事务新建连接(-C 表示 connection per transaction)
pgbench -h 10.0.0.10 -p 6432 -U appuser -c 32 -j 8 -T 60 -C appdb
# 2) 复用连接
pgbench -h 10.0.0.10 -p 6432 -U appuser -c 32 -j 8 -T 60 appdb典型结果形态(数值随硬件变化,仅示意差异量级):
-C(每次建连接): tps = 3800 latency average = 8.4 ms
复用连接 : tps = 14500 latency average = 2.2 ms两者的 tps 差距,就是连接建立成本在真实负载下的体现。若差距显著,说明池化收益明确。
也可以用 PgBouncer 自身的 SHOW STATS 观察平均查询等待时间:
SHOW STATS; database | total_xact_count | total_query_count | avg_query_time | avg_wait_time
----------+------------------+-------------------+----------------+--------------
appdb | 8420311 | 25130422 | 312 | 4avg_wait_time(单位微秒)是客户端在池中排队等待的平均时间。它一旦升高,说明池容量开始成为瓶颈。
注意事项
1. pool_mode = transaction 是默认要改的第一件事
PgBouncer 默认 session 模式,此时服务端连接被客户端独占,池化收益极小。上线前务必确认改成了 transaction(除非确实有会话状态需求)。
2. 别把 max_client_conn 当成「数据库能承受的连接数」
它只是 PgBouncer 内存中维护的客户端数量上限。真正打到 PostgreSQL 的是 default_pool_size 之和。
3. 所有池容量之和必须小于 max_connections
Σ (default_pool_size + reserve_pool_size) < max_connections - superuser_reserved_connections否则 PostgreSQL 会直接报 FATAL: sorry, too many clients already。
4. transaction 模式下不要用会话级 SET
-- 错误:下个事务可能落到另一条服务端连接上,设置丢失
SET search_path = app, public;
-- 推荐:事务级设置,随事务结束自动失效
SET LOCAL search_path = app, public;同理,时区、statement_timeout 等会话参数应通过连接串或 SET LOCAL 设置。
5. 长事务会「霸占」服务端连接
事务模式下,服务端连接只在事务结束才归还。一个挂起的长事务会让该连接无法服务其他客户端,从而放大 cl_waiting。务必排查 idle in transaction:
SELECT pid, state, now() - state_change AS idle_for, left(query, 60)
FROM pg_stat_activity
WHERE state = 'idle in transaction'
ORDER BY state_change;6. LISTEN / NOTIFY 与通知类功能不要走事务池
通知机制依赖固定的会话连接。需要时给这类客户端单独配一个 session 模式的池或直连端口。
7. 事务级 advisory lock 才是池化环境的正确选择
-- 风险:会话级锁,连接复用后语义混乱
SELECT pg_advisory_lock(12345);
-- 推荐:事务级锁,提交/回滚自动释放
SELECT pg_advisory_xact_lock(12345);8. 连接池掩盖不了 SQL 问题
连接池解决的是「连接开销」,不是「查询慢」。如果单条 SQL 要跑 5 秒,池化只会把等待从「建连接」转移到「排队」。慢 SQL 仍要靠索引与执行计划优化解决(见 查询重写技巧)。
9. server_lifetime 与 server_idle_timeout 都别设太大
服务端连接长期不重建,会固定占用后端内存,也不利于负载分散。默认的 3600s / 600s 通常够用。反之设得过小会导致频繁重建连接,收益下降。
10. 云托管服务的「连接池」能力有限
RDS/Aurora/Cloud SQL 提供的连接池(如 RDS Proxy、Cloud SQL Auth Proxy 的连接池)多为事务模式且行为受限,仍建议在应用与数据库之间自建 PgBouncer 来获得可控性。
11. pgbouncer 数据库必须用管理员账号连接
SHOW POOLS、SHOW STATS 等管理命令要求在 [pgbouncer] 段配置 admin_users(或 stats_users),且用 psql -U pgbouncer pgbouncer 的方式连接,不能走业务库名。
12. 别忘了给 PgBouncer 本身做高可用
PgBouncer 是单进程。它挂掉后所有应用连接中断。生产环境至少部署两个实例,用虚拟 IP 或服务发现做故障转移。
最佳实践
1. OLTP 场景起步配置
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25 ; 起步值,按 CPU 核数 × 2~4 调整
reserve_pool_size = 10
query_wait_timeout = 30
server_idle_timeout = 300
server_lifetime = 18002. 池大小的计算口径
单池 default_pool_size ≈ min(
CPU 物理核数 × 2~4, -- CPU 限制
max_connections × 0.6 / 池数 -- 总容量限制
)3. 给应用连接加上 application_name
便于在 pg_stat_activity 里区分来源,池化后这一点尤其重要:
appdb = host=10.0.0.10 dbname=appdb application_name=order-svc4. 监控三条线:连接数、等待时间、池饱和度
-- PostgreSQL 侧:总连接数与状态分布
SELECT state, count(*)
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY state
ORDER BY count(*) DESC;-- PgBouncer 侧:等待队列与池饱和度(cl_waiting、sv_active)
SHOW POOLS;-- PgBouncer 侧:平均等待时间趋势
SHOW STATS;三类指标触发的动作:
| 指标 | 阈值建议 | 动作 |
|---|---|---|
PostgreSQL 连接数 / max_connections | > 80% | 检查是否有实例绕过池直连 |
cl_waiting | 长期 > 0 | 排查慢事务;确认 CPU 有余量后扩池 |
avg_wait_time | 持续上升 | 池容量或后端性能不足 |
idle in transaction 会话 | 存在且持续 > 数分钟 | 立即排查应用事务管理 |
5. 上线前做一次「容量推演」
已知:应用实例 4 个 × worker 200 = 800 潜在连接
设定:PgBouncer default_pool_size = 60,reserve_pool_size = 20
核算:80 × 1(单库单用户) = 80 条服务端连接
80 < max_connections(300) - 3 ✅
80 ≈ 16 核 × 5,需实测确认 p99 可接受6. 灰度切换,保留回退通道
切换应用连接串到 PgBouncer 时,先在非核心业务验证,确认预处理语句、SET、事务行为都正常后再全量。回退只需改回直连端口。
参考链接
- 运行时配置:连接(Connections and Authentication)
- 协议级预处理语句(
max_prepared_statements) - 监控统计视图(The Cumulative Statistics System)
- 进程模型与后端进程(PostgreSQL Architectural Fundamentals)
- 预编译语句(PREPARE)
- PgBouncer 官方文档
- PostgreSQL 18 发布说明(Release 18)
下一节
继续学习:WAL 与检查点优化