PgBouncer 连接池
PgBouncer 是 PostgreSQL 生态中最常用的轻量级连接池(connection pooler)。它以一个独立进程的形式运行在应用与数据库之间:前端接受应用的连接,后端维护一小组到 PostgreSQL 的真实连接,并在两者之间复用。对应用而言,它表现为一个「高度兼容 PostgreSQL 线协议」的服务器;对 PostgreSQL 而言,它把成百上千的客户端连接收敛成几十个后端进程,把连接开销压缩一两个数量级。
PostgreSQL 的高可用架构里几乎总能看到 PgBouncer 的身影:故障转移后连接需要尽快指向新主库;读写分离会产生大量只读连接;微服务化之后连接数进一步膨胀——这些都超出了 PostgreSQL 原生连接模型能舒服承受的范围。连接池是「连接路由层」的第一块积木:本页先讲清为什么需要它,再逐项梳理三种池化模式、关键配置与管理命令,最后给出一个可直接落地的 transaction 模式实战配置。
需要提前建立一个心智模型:PgBouncer 不是「缓存代理」,也不做读写路由,它只做一件事——复用后端连接。理解这一点,就能明白为什么它在 transaction 模式下对会话状态有一系列限制,也能明白它与 HAProxy(TCP 负载均衡)、Patroni(高可用编排)各自处于完全不同的层次,三者互相配合而不互相替代。
前置知识
建议先阅读:自动故障转移
基础概念
| 概念 | 英文 / 记法 | 说明 |
|---|---|---|
| 连接池 | connection pool | 在客户端与数据库之间复用少量后端连接的中间层 |
| 前端连接 | client connection | 应用连到 PgBouncer 的连接(虚拟连接,开销小) |
| 后端连接 | server connection | PgBouncer 连到 PostgreSQL 的真实连接(每条一个后端进程) |
| 池 | pool | 按「数据库 × 用户」组合划分的后端连接集合 |
| 池化模式 | pool_mode | 何时归还后端连接:session / transaction / statement |
| 管理控制台 | admin console | 连接到名为 pgbouncer 的虚拟库执行管理命令 |
| 认证文件 | auth_file | 存放用户名与口令(或 SCRAM 校验器)的 userlist.txt |
| 服务端复位查询 | server_reset_query | 后端连接归还前执行的清理语句,默认 DISCARD ALL |
为什么 PostgreSQL 需要连接池
PostgreSQL 的连接模型是每连接一进程(process per connection):客户端每建立一个连接,postmaster 就 fork 一个独立的 backend 进程为其服务。这个模型带来三个直接后果:
- 内存开销大。每个 backend 进程自带一份基础内存(连接状态、catalog 缓存、各类缓冲),基线就是数 MB;再加上查询执行时按需分配的
work_mem、maintenance_work_mem,几百个连接就可能吃掉数 GB 内存。 - 建立连接慢。fork 进程、初始化 catalog、完成认证,整个握手要经历多次系统调用与磁盘读取。典型路径下建立一条连接需要几十毫秒,短连接高频的应用会把大量 CPU 花在「建连接」本身上。
max_connections是硬上限。超过上限的连接请求直接被拒绝(FATAL: too many connections),而且把这个值调大并不能解决问题:更多 backend 进程意味着更多内存与上下文切换,实测连接数上千后性能反而下降。
连接池的思路很直接:让少量真实后端连接服务大量客户端连接。客户端到 PgBouncer 的连接只是用户态的协议处理,代价极低;PgBouncer 到 PostgreSQL 的连接才是真正的进程,数量控制在几十个以内,数据库始终保持「少进程、高复用」的健康状态。
连接池在高可用架构中的位置
在「流复制(数据层)+ 外部编排(决策层)+ 连接路由(应用层)」的分层里,PgBouncer 属于最贴近应用的连接路由层,通常与 HAProxy、Patroni 这样配合:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 应用 A │ │ 应用 B │ │ 应用 C │ 数百个客户端连接
└────┬────┘ └────┬────┘ └────┬────┘
└────────────┼────────────┘
▼
┌───────────────────┐
│ PgBouncer │ 监听 6432,协议级复用
└─────────┬─────────┘
▼
┌───────────────────┐
│ HAProxy / VIP │ 角色识别与路由(可选)
└─────────┬─────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 主库 │←─│ 备库 1 │ │ 备库 2 │ 流复制,见流复制原理
└─────────┘ └─────────┘ └─────────┘PgBouncer 本身是单点进程,自身的高可用要靠部署方式解决(每台应用服务器各跑一个,或放在 HAProxy 之后),这会在后文展开。
语法与用法
PgBouncer 的全部行为由一个 pgbouncer.ini 决定,文件由三个部分组成:[databases] 定义「对外暴露的库名 → 真实数据库的映射」,[pgbouncer] 放全局参数,[users] 做按用户的参数覆盖。一个最小可用的配置:
[databases]
# 对外暴露库名 mydb,实际连到本机 5432 的 mydb
mydb = host=127.0.0.1 port=5432 dbname=mydb
[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
admin_users = postgres
stats_users = postgres
pool_mode = transaction
max_client_conn = 500
default_pool_size = 25启动、停止与访问管理控制台:
# 前台启动(调试用)
pgbouncer /etc/pgbouncer/pgbouncer.ini
# 后台启动(守护模式,需配置 pidfile 与 logfile)
pgbouncer -d /etc/pgbouncer/pgbouncer.ini
# 连接管理控制台:连接到一个名为 pgbouncer 的虚拟数据库
psql -h 127.0.0.1 -p 6432 -U postgres pgbouncer -c "SHOW POOLS;" database | user | cl_active | cl_waiting | sv_active | sv_idle | sv_used | sv_tested | sv_login | maxwait | pool_mode
--------------+-------------+-----------+------------+-----------+---------+---------+-----------+----------+---------+-----------
mydb | app | 118 | 2 | 25 | 5 | 0 | 0 | 0 | 812 | transaction
pgbouncer | postgres | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | statement
(2 rows)应用侧的连接串只需把端口与地址改成 PgBouncer 的监听项,其余完全不变:
# 直连 PostgreSQL
psql "host=192.168.1.10 port=5432 dbname=mydb user=app"
# 经 PgBouncer 连接
psql "host=192.168.1.10 port=6432 dbname=mydb user=app"详细说明
三种池化模式
pool_mode 是 PgBouncer 最重要的一个参数,它决定了后端连接何时可以被另一个客户端复用,进而决定了兼容性与性能之间的取舍。
| 模式 | 后端连接归还时机 | 会话状态 | 典型场景 |
|---|---|---|---|
session | 客户端断开后归还 | 完整保留 | 传统长连接应用,依赖会话级特性(临时表、LISTEN/NOTIFY、会话级 PREPARE) |
transaction | 每个事务结束后归还 | 不保留 | 绝大多数 Web 应用、ORM、短事务高并发场景 |
statement | 每条语句结束后归还 | 不保留,且禁止多语句事务 | 强制自动提交的简单查询场景(如部分 BI 工具) |
三者的复用粒度逐级变细,连接复用率与吞吐也逐级变高,但对会话状态的破坏也逐级加重:
- session 模式下,客户端独占后端连接直到断开,行为与直连 PostgreSQL 几乎一致,只是省掉了新建连接的成本。这是兼容性最好的模式,但「一客户端一进程」的内存问题并没有真正缓解,只解决了「建连接慢」。
- transaction 模式是生产环境的主流选择:同一个客户端在事务 A 结束、事务 B 开始之间,可能被切换到另一条后端连接。任何「写在连接上、而非写在数据里」的状态都可能失效,具体限制见下文。
- statement 模式进一步把粒度压到单条语句,显式事务会被直接拒绝(报错
long transactions not allowed)。它只适合明确「全部自动提交」的工作负载。
模式可以按库、按用户覆盖
[databases] 段的连接串里可以写 pool_mode=session,[users] 段可以为单个用户设置 pool_mode,从而实现「报表用户用 transaction、管理工具用 session」的混合策略。全局 pool_mode 只是没有被覆盖时的默认值。
覆盖的优先级是:用户设置 > 数据库设置 > 全局默认。利用这一点可以把「需要会话特性的少数模块」隔离出来,而让主体流量继续享受 transaction 模式的复用收益:
[databases]
; 主体业务库:跟随全局 transaction 模式
mydb = host=192.168.1.100 port=5432 dbname=mydb
; 依赖会话级特性的遗留应用:单独强制 session 模式
legacy = host=192.168.1.100 port=5432 dbname=legacy pool_mode=session
[users]
; 报表账号:限制最大后端连接,避免长查询挤占在线业务
report = pool_mode=transaction max_user_connections=10
; 核心业务账号:允许借用自己的应急备用连接
app = pool_mode=transaction reserve_pool_size=3[users] 段可覆盖的常见键包括 pool_mode、max_user_connections、reserve_pool_size 等(完整列表以官方 config 文档为准)。注意 [users] 里的用户名必须同时出现在 auth_file(或能通过 auth_query 查到),否则该段配置不会被使用。
连接的生命周期与 SHOW POOLS 状态机
理解 PgBouncer 的运行状态,主要看 SHOW POOLS 里 sv_* 系列字段,它们对应后端连接的状态机:
| 字段 | 含义 |
|---|---|
cl_active | 已挂接到后端连接、可处理查询的客户端连接 |
cl_waiting | 已发出请求但还没拿到后端连接的客户端连接(排队中) |
sv_active | 正在服务某个客户端的后端连接 |
sv_idle | 空闲且立即可用的后端连接 |
sv_used | 空闲但超过 server_check_delay 未被触碰、复用前需先做 server_check_query 探活的后端连接 |
sv_tested | 正在执行 server_reset_query 或 server_check_query 的后端连接 |
sv_login | 正在登录 PostgreSQL 的后端连接 |
maxwait | 排队队列里最早那个客户端已等待的秒数 |
一条后端连接的一生:登录(sv_login)→ 服务客户端(sv_active)→ 事务结束归还(sv_idle)→ 长期未用需探活(sv_used)→ 超过 server_lifetime 或 server_idle_timeout 被关闭。健康状态下 cl_waiting 应接近 0、maxwait 应接近 0;两者持续增长说明池太小或数据库变慢了。
认证配置(auth_type / auth_file / auth_query)
PgBouncer 需要同时面对两类认证问题:怎么验证客户端,以及拿什么凭据去连真正的 PostgreSQL。
auth_type 决定对客户端的认证方式:
| 取值 | 行为 |
|---|---|
scram-sha-256 | 推荐。使用 SCRAM-SHA-256 握手,口令明文不必离开客户端;要求 auth_file 里存 SCRAM 校验器 |
md5 | 使用 MD5 挑战握手(md5 + md5(口令+用户名))。PostgreSQL 18 已弃用 MD5 口令,新环境不要再用 |
cert | 基于 TLS 客户端证书认证,配合 client_tls_sslmode=require 使用 |
trust | 不做口令校验,仅适合内部网络或测试 |
hba | 复用 PostgreSQL 风格的 HBA 规则文件(auth_hba_file),可按来源地址区分认证方式 |
pam | 交给系统 PAM 校验(编译时需启用) |
auth_file(常叫 userlist.txt)的格式是每行一个用户,字段用双引号包裹:
"postgres" "SCRAM-SHA-256$4096:abcdef...$abc123...:def456..."
"app" "SCRAM-SHA-256$4096:abcdef...$aaa...:bbb..."
"ro" "md5b3c5a2..."口令字段可以是 SCRAM 校验器(与 pg_authid.rolpassword 内容一致,可直接从数据库导出),也可以是 md5 哈希。使用 scram-sha-256 时,把 pg_authid 里的校验器原样拷过来即可,PgBouncer 无需知道明文口令就能完成对客户端的 SCRAM 认证——但它连后端时也需要能通过 PostgreSQL 的认证,因此后端也必须使用 SCRAM 且校验器可用。
当用户很多、不想维护 auth_file 时,可以用 auth_query 让 PgBouncer 现场向 PostgreSQL 查口令:以 auth_user 的身份连到目标库执行 auth_query(默认值为 SELECT usename, passwd FROM pg_shadow WHERE usename=$1)。由于普通用户无权读 pg_shadow,需要在数据库里预先创建一个 SECURITY DEFINER 的包装函数(官方 FAQ 提供的做法):
-- 以管理员身份在业务库中执行
CREATE SCHEMA pgbouncer AUTHORIZATION admin;
CREATE FUNCTION pgbouncer.get_auth(p_usename TEXT)
RETURNS TABLE (username TEXT, password TEXT)
LANGUAGE sql SECURITY DEFINER AS $$
SELECT usename::TEXT, passwd::TEXT
FROM pg_shadow
WHERE usename = p_usename;
$$;
REVOKE ALL ON FUNCTION pgbouncer.get_auth(p_usename TEXT) FROM PUBLIC;[pgbouncer]
auth_type = scram-sha-256
auth_query = SELECT usename, passwd FROM pgbouncer.get_auth($1)
auth_user = adminauth_file 与 auth_query 可以叠加
PgBouncer 先查 auth_file,查不到再走 auth_query。实践中常见「管理员账号放 auth_file,业务账号走 auth_query」的组合,避免每次新增用户都要改文件并 RELOAD。
连接限额与超时参数
限额类参数回答「最多允许多少连接」;超时类参数回答「一条连接闲置或等待多久后被回收」。以下默认值以官方 config 文档为准:
| 参数 | 默认值 | 说明 |
|---|---|---|
listen_addr / listen_port | localhost / 6432 | 监听地址与端口,对外服务通常改为 * 或内网地址 |
max_client_conn | 100 | 前端连接总上限。超出的连接立刻被拒绝,这是应用侧最需要感知的数字 |
default_pool_size | 20 | 每个「数据库 × 用户」池的后端连接数 |
min_pool_size | 0 | 池中至少维持的后端连接数(有客户端连接过该池才生效) |
reserve_pool_size | 0 | 池满时的应急备用连接数 |
reserve_pool_timeout | 5 | 客户端等待超过该秒数后启用备用连接 |
max_db_connections | 0(不限) | 单个数据库(跨所有用户池)的后端连接上限 |
max_user_connections | 0(不限) | 单个用户(跨所有数据库池)的后端连接上限 |
server_idle_timeout | 600 | 后端连接空闲多久后关闭(秒) |
client_idle_timeout | 0(禁用) | 前端连接空闲多久后强制断开(秒)。默认不清理 |
client_login_timeout | 15 | 客户端登录超时(秒),超时未完成认证则断开 |
query_wait_timeout | 120 | 客户端在排队队列中等待后端连接的最大秒数,超时报错释放 |
server_lifetime | 3600 | 后端连接最长存活时间(秒),到期后在空闲时关闭,防止连接长期驻留 |
server_connect_timeout | 15 | 连接后端 PostgreSQL 的超时(秒) |
几个参数之间的联动值得专门说明:
max_client_conn是前端总量上限,与后端无关;真正决定 PostgreSQL 进程数的是default_pool_size× 池的个数(每个「数据库 × 用户」组合一个池),再叠加reserve_pool_size与max_db_connections/max_user_connections的封顶。query_wait_timeout是 transaction 模式下的关键安全阀:排队等待超过它的客户端会收到错误并释放。如果它远大于业务可容忍的响应时间,用户看到的是「网站很慢」;设置得当则快速失败,把问题暴露给监控而不是用户。min_pool_size适合「报表、批处理偶发突刺」的池:平时预热几条连接,避免突刺时全部现场登录。注意它只在「有客户端连过」的池上生效,冷池不会被凭空预热。
server_reset_query 与状态清理
后端连接被复用意味着前一个客户端留下的状态可能污染后一个。PgBouncer 的对策是 server_reset_query,默认值为:
DISCARD ALL它在 session 模式下、客户端断开连接归还后端时执行,把会话变量、 prepared statements、临时表、游标等全部清干净。在 transaction 模式下,事务边界本身已经隐含了大部分清理(事务回滚会撤销未提交状态),因此 server_reset_query 默认不在 transaction 模式执行;如果确有需要,可以打开 server_reset_query_always = 1 强制每条连接归还时都执行——但这会给每个事务增加一次 DISCARD ALL 的往返,一般不建议。
与它相关的还有两个探活参数:server_check_query(默认 select 1)与 server_check_delay(默认 30 秒),用来对「闲置超过一段时间」的后端连接在复用前做廉价探活,避免把请求派给已经死掉的连接。
不支持或受限的功能
transaction 模式是性能与兼容性的交换,代价集中在「会话状态」上。以官方 FAQ 与文档为准,下列功能在 transaction 模式下不可用或行为受限:
| 功能 | transaction 模式下的表现 | 说明 |
|---|---|---|
会话级 SET | 允许执行,但只影响当前这条后端连接,且事务结束后客户端可能被切走,效果不可预期 | 应改用事务内的 SET LOCAL,或写进 postgresql.conf |
LISTEN / NOTIFY | 不支持 | 通知注册在一条后端连接上,客户端切走后收不到 |
会话级 PREPARE / DEALLOCATE(SQL 层) | 不支持 | prepared statement 挂在特定后端连接上,复用后找不到 |
| 协议级 prepared statement | 1.21 起在 max_prepared_statements > 0 时支持自动跟踪与重挂接 | 较老版本运行 ORM/驱动会报 prepared statement "..." does not exist |
WITH HOLD 游标 | 不支持 | 提交后仍需跨事务保持的游标无法跨连接存活 |
| 临时表 | 不可靠 | 表建在一条后端连接上,后续语句可能落到另一条连接 |
会话级 advisory lock(pg_advisory_lock) | 不可靠 | 锁挂在某条后端连接上,切走后既持有不了也释放不掉 |
COPY 大数据量 | 可用,但会长时间独占一条后端连接 | 相当于一个长事务,会降低池的复用率 |
SET SESSION AUTHORIZATION | 受限 | 同会话级 SET 的问题 |
会话级 advisory lock 要换成事务级 advisory lock(pg_advisory_xact_lock),事务结束自动释放,天然与 transaction 模式兼容;游标优先改成普通查询或分页;临时表改成真实表或 CTE。这些改写是「把状态从连接挪进事务」的共同思路。
管理控制台
连接到名为 pgbouncer 的虚拟数据库(psql -p 6432 pgbouncer),即可执行管理命令。admin_users 中的用户可执行全部命令,stats_users 只能执行 SHOW 类命令。
| 命令 | 作用 |
|---|---|
SHOW POOLS | 查看各池的前端/后端连接分布与排队情况 |
SHOW CLIENTS | 列出所有前端连接(含等待时间、所属池) |
SHOW SERVERS | 列出所有后端连接(含当前状态、事务时间) |
SHOW STATS | 累计统计:事务数、查询数、平均耗时、等待时间(单位微秒) |
SHOW DATABASES | 查看 [databases] 定义的库映射 |
SHOW USERS | 查看用户与各自的池化设置 |
SHOW CONFIG | 查看当前生效的全部配置 |
SHOW LISTS | 一次性输出用户/数据库/池等清单 |
RELOAD | 重读 pgbouncer.ini,多数参数热生效,不断开现有连接 |
PAUSE [db] | 暂停:等待所有事务结束后断开后端连接(配合切换、升级使用) |
RESUME [db] | 恢复被 PAUSE 的库 |
DISABLE db / ENABLE db | 禁止/允许新客户端连接指定库 |
KILL db | 立即断开该库的所有前端与后端连接(暴力操作) |
SUSPEND | 冲刷所有缓冲后挂起(配合在线升级使用) |
RECONNECT [db] | 让后端连接在空闲时重新建立(配合故障转移切到新主库) |
SHUTDOWN | 停止 PgBouncer 进程 |
pgbouncer=# RELOAD;
RELOAD
pgbouncer=# PAUSE mydb;
PAUSE
pgbouncer=# RESUME mydb;
RESUMEPAUSE 与 RECONNECT 是高可用切换里最常用的两条:前者配合 PostgreSQL 的小版本升级(先把前端连接挡在事务边界之外),后者让所有后端连接尽快重新指向新主库。
监控指标与 Prometheus exporter
PgBouncer 自带的 SHOW STATS / SHOW POOLS 就是监控数据源,Prometheus 生态的 pgbouncer_exporter 会以 stats_users 身份定期抓取这些命令并转成指标。核心监控点:
| 指标来源 | 关注点 | 告警建议 |
|---|---|---|
SHOW POOLS 的 cl_waiting / maxwait | 排队程度 | maxwait 持续 > 1 秒告警 |
SHOW STATS 的 waited_time | 客户端累计等待时间占比 | 环比明显上升告警 |
前端连接数 vs max_client_conn | 连接耗尽风险 | 使用率 > 80% 告警 |
后端连接数 vs default_pool_size × 池数 | 池是否已打满 | 池满伴随排队告警 |
日志中的 query_wait_timeout 报错 | 排队超时快速失败 | 出现即告警(数据库或池已饱和) |
# 手动抓取统计,确认 exporter 依赖的账号可用
psql -h 127.0.0.1 -p 6432 -U postgres pgbouncer -c "SHOW STATS;" database | xact_count | query_count | xact_time | query_time | waited_time
----------+------------+-------------+-----------+------------+-------------
mydb | 158342 | 274561 | 12035 | 8211 | 3122
(1 row)部署与 systemd 服务
生产部署有两种主流形态:每台应用服务器本地跑一个 PgBouncer(监听 127.0.0.1,天然无单点),或独立服务器集中部署(性能好管理方便,但要解决自身高可用)。无论哪种,都建议用 systemd 托管进程:
[Unit]
Description=PgBouncer connection pooler
After=network.target
[Service]
Type=forking
User=postgres
ExecStart=/usr/bin/pgbouncer -d /etc/pgbouncer/pgbouncer.ini
PIDFile=/run/postgresql/pgbouncer.pid
ExecReload=/bin/kill -HUP $MAINPID
LimitNOFILE=65536
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now pgbouncer
sudo systemctl status pgbouncer
# 修改配置后热加载(不中断现有连接)
psql -p 6432 pgbouncer -c "RELOAD;"两个部署细节容易踩坑:其一,max_client_conn 会被进程的文件描述符上限卡住,systemd 单元里要把 LimitNOFILE 调大;其二,Debian 系发行版的软件包自带 pgbouncer.service,路径与用户(通常为 postgres)与本例可能不同,以发行版包内单元为准。
自身高可用的常见做法:把 PgBouncer 与应用同机部署(单点即应用自身,可接受);或者两台 PgBouncer 前面加 HAProxy/Keepalived VIP——这正好衔接下一页的 HAProxy / Keepalived。
与 HAProxy / Patroni 的配合
PgBouncer 只管复用连接,不管「连到哪台 PostgreSQL」,角色识别要交给下游组件:
- 配合 Patroni + HAProxy:PgBouncer 的
[databases]指向 HAProxy 的写端口(如:5000),HAProxy 通过 Patroni 的/primary端点识别主库。故障转移后,PgBouncer 原有的后端连接会因为主库角色变化而断开重连,必要时在管理台执行RECONNECT加速。 - 配合 VIP(Keepalived):
[databases]直接写 VIP 地址,主库切换时 VIP 漂移,PgBouncer 的后端连接断开重连后自然落到新主库。 - 每应用服务器本地部署时,PgBouncer 位于应用与 HAProxy/VIP 之间,故障转移对应用完全透明。
一个容易忽视的点:PgBouncer 会把「后端连接断了」表现为前端连接收到错误,应用必须具备重试能力,不能假设一条连接永远可用。在总是发生主备切换的高可用环境里,这一点与配置本身同等重要。
池大小估算经验
池不是越大越好。每个后端连接都是 PostgreSQL 里的一个真实进程,连接数超过 CPU 核数的若干倍后,上下文切换与锁竞争会拖慢所有查询。社区流传的经典起点公式(源自 PostgreSQL Wiki):
pool_size ≈ (CPU 核数 × 2) + 有效磁盘轴数(SSD/NVMe 环境可忽略磁盘项)按经验展开为几条规则:
- 以 8 核数据库为例,
default_pool_size从 20~30 起步,通过压测与cl_waiting/maxwait观察调整,而不是盲目调到几百。 max_client_conn可以给得宽裕(几百到几千),它是前端虚拟连接,代价小;后端连接数才是要严格控制的。- 多个业务用户、多个数据库会形成多个池,总后端连接数 = 各池之和,务必用
max_db_connections给单库封顶。 - 批处理、报表这类长事务要单独一个用户/库走独立池,避免占满共用池把在线业务饿死。
- 调整后观察
SHOW STATS的平均耗时与waited_time:如果等待时间在涨而查询耗时没降,说明池或数据库已经饱和,加连接只会更糟。
一个完整的算例帮助建立量感:16 核、64GB 内存、NVMe 存储的 PostgreSQL 实例,max_connections = 200,前端有 3 个应用、合计峰值 600 个并发连接。
default_pool_size = 32 # (16 × 2) 起步,压测微调
池的数量 = 3~4 个 # 每个业务用户一个池
后端连接总数上限 ≈ 128 # 4 池 × 32,远低于 max_connections
max_db_connections = 150 # 给单库封顶,兜底防打爆
max_client_conn = 800 # 前端宽松,覆盖峰值 600 并留余量
reserve_pool_size = 8 # 每池突发借用的应急余量600 个前端连接只消耗 128 个后端进程,PostgreSQL 始终运行在「进程数 ≈ CPU 核数两倍」的高效区间——这正是连接池的价值所在。若压测发现查询平均耗时上升且 sv_active 长期满载,应先优化 SQL 再考虑扩池。
实战示例
示例一:transaction 模式完整配置
一个面向 Web 应用、对接 HAProxy 的 transaction 模式配置,可直接作为起点:
;; /etc/pgbouncer/pgbouncer.ini
[databases]
; 应用主库:写流量经 HAProxy 写端口路由到当前主库
mydb = host=192.168.1.100 port=5000 dbname=mydb
[pgbouncer]
; ---- 网络与监听 ----
listen_addr = 0.0.0.0
listen_port = 6432
unix_socket_dir = /var/run/postgresql
; ---- 认证 ----
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
auth_query = SELECT usename, passwd FROM pgbouncer.get_auth($1)
auth_user = admin
admin_users = postgres
stats_users = postgres,monitoring
; ---- 池化 ----
pool_mode = transaction
max_client_conn = 500
default_pool_size = 25
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 5
max_db_connections = 60
; ---- 超时与清理 ----
server_idle_timeout = 300
client_idle_timeout = 0
client_login_timeout = 15
query_wait_timeout = 60
server_lifetime = 3600
server_reset_query = DISCARD ALL
ignore_startup_parameters = extra_float_digits
application_name_add_host = 1
; ---- TLS(对前端加密,可选)----
client_tls_sslmode = disable
; ---- 日志 ----
log_connections = 1
log_disconnections = 1
logfile = /var/log/postgresql/pgbouncer.log
pidfile = /run/postgresql/pgbouncer.pid# 校验能启动并查看配置是否如预期
pgbouncer -d /etc/pgbouncer/pgbouncer.ini
psql -p 6432 -U postgres pgbouncer -c "SHOW CONFIG;" | grep -E 'pool_mode|max_client_conn|default_pool_size'示例二:userlist.txt 与认证文件
# 从 PostgreSQL 导出 SCRAM 校验器,生成 userlist.txt(超管与业务账号)
sudo -u postgres psql -Atc \
"SELECT concat('\"', rolname, '\" \"', rolpassword, '\"')
FROM pg_authid
WHERE rolname IN ('postgres', 'app', 'ro')
AND rolcanlogin;" \
| sudo tee /etc/pgbouncer/userlist.txt
sudo chown postgres:postgres /etc/pgbouncer/userlist.txt
sudo chmod 640 /etc/pgbouncer/userlist.txt"postgres" "SCRAM-SHA-256$4096:vhD1...$eD3k...:r7Yp..."
"app" "SCRAM-SHA-256$4096:Kb9q...$2Hwl...:Q0sx..."
"ro" "SCRAM-SHA-256$4096:m2Fa...$9tUc...:Lz4v..."校验器与 pg_authid.rolpassword 完全一致,PgBouncer 既用它认证前端,也用它向后端 SCRAM 登录,无需任何明文口令。
示例三:管理控制台巡检
psql -p 6432 -U postgres pgbouncerpgbouncer=# SHOW POOLS;
pgbouncer=# SHOW STATS;
pgbouncer=# SHOW SERVERS;
pgbouncer=# SHOW CLIENTS;
pgbouncer=# SHOW DATABASES;
pgbouncer=# RELOAD;
RELOAD巡检时最值得记录的三个数:cl_waiting(排队量)、maxwait(最早排队者的等待秒数)、后端连接总数(与池上限比较)。把这条巡检固化到 Prometheus 抓取里,就是最朴素的池健康度监控。
示例四:故障转移后的连接切换
主库切换(Patroni 或 repmgr 完成提升)之后,PgBouncer 池里的后端连接可能还挂在旧主库的 TCP 上。按顺序处理:
# 1. 观察后端连接是否已自行恢复(多数情况会自动断开重连)
psql -p 6432 -U postgres pgbouncer -c "SHOW SERVERS;"
# 2. 未恢复时,让所有后端连接尽快重建
psql -p 6432 -U postgres pgbouncer -c "RECONNECT mydb;"
# 3. 数据库本体维护(如小版本升级)时,用 PAUSE 挡住流量
psql -p 6432 -U postgres pgbouncer -c "PAUSE mydb;"
psql -p 6432 -U postgres pgbouncer -c "RESUME mydb;"示例五:验证 transaction 模式的行为
-- 连接经 PgBouncer(6432)进入
-- 1) 会话级 SET 在事务结束后可能失效:不依赖它
SET work_mem = '256MB';
SHOW work_mem; -- 同一事务内有效
-- 2) 事务级 advisory lock 可以正常使用
SELECT pg_advisory_xact_lock(42);
-- 3) 会话级 advisory lock 在 transaction 模式下不可靠,禁止使用
-- SELECT pg_advisory_lock(42); -- 不要这样写 pg_advisory_xact_lock
-----------------------
t
(1 row)注意事项
transaction 模式下必须把会话状态包进事务。 SET 要写成事务内的 SET LOCAL,advisory lock 要用 pg_advisory_xact_lock,临时表要么改真实表要么用 ON COMMIT DROP 语义。任何「写在连接上」的状态都可能在下一条语句时落到另一条后端连接上。
不要依赖会话级 SET 做业务逻辑。 它在 transaction 模式下「看起来能跑」,实际是把配置泄漏给了同一条后端连接上的其他客户端,或自己拿不到想要的值。全局参数进 postgresql.conf,事务参数用 SET LOCAL。
LISTEN/NOTIFY 与 WITH HOLD 游标在 transaction 模式下不可用。 需要这两者的模块要直连 PostgreSQL(或对它们走 session 模式的独立池),不要指望 PgBouncer 兼容。
老版本 PgBouncer 不支持协议级 prepared statement。 升级到支持 max_prepared_statements 的版本,或把驱动改成不使用命名 prepared statement,否则会反复报 prepared statement "..." does not exist。以官方 changelog 为准确认你的版本行为。
max_client_conn 拒绝连接是瞬时失败。 应用必须有连接失败重试与退避逻辑;同时用 LimitNOFILE 保证进程描述符上限不低于 max_client_conn 的两倍以上。
PgBouncer 自身是单点。 它不做主备、不做集群。「每应用服务器本地部署」或「HAProxy/Keepalived 前置」二选一,不能裸跑在关键路径上。
后端凭据必须能同时通过 PostgreSQL 认证。 auth_type=scram-sha-256 时,auth_file 里的校验器要能用于登录后端;auth_query 路径则要求 auth_user 在每个业务库里都有对应的 get_auth 包装函数。
query_wait_timeout 是快速失败阀门,不是无脑调大。 把它设成几分钟只会让用户等几分钟再看到错误。它的量级应该贴近业务能容忍的最大响应时间。
RELOAD 与重启的生效范围不同。 RELOAD 热加载多数参数,但监听端口、认证文件路径等仍需重启;pool_mode 热切换只影响新事务,正在进行的旧模式连接不受影响。
KILL 是暴力命令。 它会同时断开前端与后端连接,只用于明确要清空某库连接的场景(例如确认连接泄漏),日常维护请用 PAUSE。
注意 ignore_startup_parameters 与驱动差异。 某些驱动/框架会在启动包里带上 PgBouncer 不认识的参数(如 extra_float_digits 的旧版本行为差异),导致连接被拒;把无需保留的参数加入 ignore_startup_parameters 即可放行,但不要把有业务语义的参数(如 options)放进去。
统计字段与单位随版本演进。 SHOW STATS 的时间类字段以微秒为单位,且新版本会增补列(如事务内耗时);监控脚本不要按「列序号」取数,按列名解析,升级前核对官方 changelog。
最佳实践
生产环境默认选 transaction 模式。 它是复用率与兼容性的最佳平衡点;确实依赖会话特性的少数模块单独开 session 池(用
[users]或[databases]覆盖)。后端连接数按「CPU 核数 × 2」量级起步,用数据调优。 观察
cl_waiting与maxwait:排队增长且查询变慢才是扩池信号;只有排队、查询不慢,优先优化慢 SQL。max_client_conn从宽、default_pool_size从严。 前端虚拟连接便宜,后端真实进程昂贵;用max_db_connections给单库封顶防止池相乘后打爆 PostgreSQL。认证统一迁移到
scram-sha-256。 MD5 在 PostgreSQL 18 已弃用;auth_file直接存pg_authid导出的 SCRAM 校验器,全程无明文。为批处理与在线业务拆分独立池。 用不同数据库用户连接,天然形成不同池,互不抢占;批处理池可单独设
max_user_connections限制。与应用同机部署 PgBouncer 是最省心的单点解法。 监听 127.0.0.1,省一次网络跳数,故障域与应用一致;集中部署则必须前置 HAProxy/Keepalived。
把
SHOW POOLS/SHOW STATS接入 Prometheus。maxwait、cl_waiting、连接使用率三条曲线足以预警 90% 的池问题,另配query_wait_timeout日志告警。故障转移演练中加入 PgBouncer 环节。 验证提升后连接能否自动恢复、
RECONNECT需不需要人工介入、应用重试是否生效——这些只有演练才能暴露。演练方法参见灾难恢复演练。对应用明确「连接可能随时断开」的契约。 高可用环境里旧主库降级、网络抖动都会切断连接,应用侧的重连与幂等重试是体系的一部分,不是可选优化。
升级 PostgreSQL 小版本时用
PAUSE/RESUME排空。 配合主备切换,让每个节点在无流量状态下重启,把中断窗口压到秒级。不要在 PgBouncer 后面再叠一层 PgBouncer。 多层池会互相掩盖排队信息,让容量规划无从下手;需要分层时用不同组件各司其职。
版本升级前读官方 changelog。 PgBouncer 的小版本也会带来行为变化(如 prepared statement 支持、统计字段增减),升级前确认与你的驱动兼容。
下一节
继续学习:HAProxy / Keepalived