一、为什么需要 PgBouncer
PgBouncer 是 PostgreSQL 生态里最轻量、最常用的连接池中间件。当应用出现短连接风暴、连接池配置不当或突发流量时,客户端会一股脑直连 PostgreSQL,很快把 max_connections 打满,新连接开始排队,延迟飙升,严重时整库雪崩。很多人第一次遇到这类问题,是在《连接池耗尽与端口耗尽排查》里看到 TIME_WAIT 堆积与连接泄漏,但那篇讲的是”怎么救火”,本文讲的是”怎么从架构上避免火灾”。PgBouncer 的核心价值,就是用连接复用,让成百上千的客户端共享少量后端数据库连接。
PostgreSQL 每个连接都会占用一个独立后端进程(约 5–10MB 私有内存),连接数越高,上下文切换与锁竞争越重。把连接数控制在合理范围,往往是性价比最高的数据库性能优化——而 PgBouncer 正是做这件事的标准答案。
二、三种池化模式怎么选
PgBouncer 的 pool_mode 决定了一条后端连接何时被复用,这是调优的命门。三种模式对比如下:
| 模式 | 复用粒度 | 适用场景 | 限制 |
|---|---|---|---|
| session(会话) | 客户端断开才释放 | 需保持会话级状态,如临时表、预备语句 | 几乎等价于直连,省不了多少连接 |
| transaction(事务) | 事务结束即归还 | 绝大多数 Web / API 服务 | 跨事务不能依赖会话状态 |
| statement(语句) | 每条语句结束即归还 | 极短查询、无事务 | 不允许多语句事务,极少见 |
2.1 Session 会话模式
客户端从连接到断开,后端连接全程独占。它的行为最接近直连,PgBouncer 几乎只起”代理”作用,连接数不会下降。仅在你的应用强依赖会话级对象(如 CREATE TEMP TABLE、SET LOCAL、服务端预备语句)时才用它。
2.2 Transaction 事务模式(推荐)
客户端开启事务时从池里取一条后端连接,COMMIT/ROLLBACK 后立刻归还。同一客户端在两次事务之间可能命中不同的后端连接。这是绝大多数 Web 服务的甜点区:一个只处理几毫秒事务的请求,不再长期霸占一条重量级连接。绝大多数场景下,首选 transaction 模式。
2.3 Statement 语句模式
粒度细到每条 SQL,连接用完即还。它要求客户端必须把多条语句包成事务,否则第二条语句会报错,实战中极少使用,一般只在极其特殊的只读高并发查询场景考虑。
三、用 Docker Compose 跑起来
最快的验证方式是用容器。下面这个编排同时起了 PostgreSQL 与 PgBouncer,应用只连 6432 端口即可:
services:
postgres:
image: postgres:16
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: apppass
POSTGRES_DB: mydb
ports:
- "5432:5432"
pgbouncer:
image: edoburu/pgbouncer:1.22
environment:
DB_HOST: postgres
DB_PORT: "5432"
DB_NAME: mydb
DB_USER: appuser
DB_PASSWORD: apppass
POOL_MODE: transaction
MAX_CLIENT_CONN: "1000"
DEFAULT_POOL_SIZE: "20"
ports:
- "6432:6432"
depends_on:
- postgres
如果你用自定义配置而非环境变量镜像,核心的 pgbouncer.ini 长这样:
[databases]
mydb = host=127.0.0.1 port=5432 dbname=mydb
[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
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 3
server_idle_timeout = 60
server_lifetime = 3600
server_connect_timeout = 15
log_connections = 1
log_disconnections = 1
admin_users = pgbouncer
stats_users = stats
账号密码放在 userlist.txt,格式是明文用户名加 md5 密文:
"appuser" "md5d7d8808a4cd53bcaSQLhash示例请替换为真实md5"
四、核心参数调优
参数调优的本质,是让”客户端并发”与”后端连接数”解耦。先把关键参数记牢:
| 参数 | 含义 | 建议值 |
|---|---|---|
| max_client_conn | PgBouncer 接受的最大客户端连接 | 1000 起,看机器 |
| default_pool_size | 每个 (user,db) 的默认后端连接数 | 20(事务模式) |
| min_pool_size | 保活的最小连接,避免冷启动建连 | 5 |
| reserve_pool_size | 峰值时的应急连接 | 5 |
| server_idle_timeout | 空闲后端连接回收秒数 | 60 |
| server_lifetime | 连接最长存活,防长连接老化 | 3600 |
4.1 pool_mode 与 default_pool_size
事务模式下,default_pool_size 决定了”同时能跑几个事务”。若你的 PG 实例 max_connections=200,而你有 10 个 (user,db) 组合,每个 pool_size 设 20 就正好打满;设太大反而压垮后端。经验值:事务模式 20 起步,结合 PG 的 max_connections 反推上限。
4.2 max_client_conn 与 max_db_connections
max_client_conn 是”门口能挤多少人”,可以远大于真实后端连接数;max_db_connections 则能按单个数据库死死卡住上限,防止某个库把连接吃光连累其他库。多租户或一实例多库时强烈建议设置。
4.3 超时与健康检查
server_connect_timeout 控制建连超时;server_idle_timeout 回收长期空闲连接,避免后端积累僵尸连接。配合 《PostgreSQL 慢查询优化》 一起看,连接池解决了”连接数”,索引与执行计划解决”单条慢”,二者是正交的两层优化。
五、与 PostgreSQL 配合的坑
5.1 事务模式下服务端预备语句失效
这是踩得最多的坑。事务模式下一连结束连接就归还池中,下一个事务可能拿到另一条后端连接,跨事务的服务端预备语句(server-side prepared statement)会”找不到”。现象是偶发报错、且难以复现。解决办法是让客户端改用客户端侧预备,或关闭服务端预备:
# JDBC:关闭服务端预备语句(事务模式必备)
jdbc:postgresql://pgbouncer:6432/mydb?prepareThreshold=0
# psql / libpq 环境变量
PGOPTIONS="-c default_transaction_pool=..." # 仅在 session 模式有意义
Python 的 psycopg2、Node 的 pg 默认就是客户端侧预备,一般无需改动;而 JDBC、Go 的 database/sql 往往默认开启服务端预备,务必显式关掉。
5.2 用户与数据库级覆盖
在 [databases] 里可以给单个库单独设 pool_mode 和 pool_size,给个别需要会话状态的库开 session 模式,其余保持 transaction。这种”按库差异化”能兼顾兼容与性能。
[databases]
oltp_db = host=127.0.0.1 port=5432 dbname=oltp_db pool_mode=transaction pool_size=30
report_db = host=127.0.0.1 port=5432 dbname=report_db pool_mode=session pool_size=10
六、生产避坑与监控
上线前对照这份清单自查:
- 应用连接串指向 6432(PgBouncer)而非 5432(直连),否则池化形同虚设。
- JDBC / Go 客户端已关闭服务端预备语句(
prepareThreshold=0)。 default_pool_size × 库数不超过 PGmax_connections。- 监控
cl_waiting(等待连接的客户端数),持续大于 0 说明池太小。 - 用
SHOW POOLS观察cl_active / sv_active / sv_idle。
PgBouncer 自带虚拟库 pgbouncer,可以直接用 SQL 查运行时状态:
# 连接管理库
psql -p 6432 -U stats -d pgbouncer -c "SHOW POOLS;"
psql -p 6432 -U stats -d pgbouncer -c "SHOW STATS;"
psql -p 6432 -U stats -d pgbouncer -c "SHOW SERVERS;"
# 临时改参数(reload 生效)
psql -p 6432 -U pgbouncer -d pgbouncer -c "RELOAD;"
把 SHOW POOLS 的输出接到 Prometheus + Grafana,就能看到连接等待、每秒查询等曲线,提前发现池瓶颈。关于搭建监控面板,可参考站内 Prometheus + Grafana 监控面板 的通用做法。
七、总结
PgBouncer 不是银弹,但它是 PostgreSQL 高并发架构里几乎必装的一层。记住三条:第一,Web 服务默认用 transaction 模式;第二,default_pool_size 由后端 max_connections 反推,别拍脑袋;第三,事务模式下务必关闭客户端服务端预备语句。把连接数这层治理好,再配合 PostgreSQL 基础能力 与 逻辑复制 等高可用手段,你的数据库才真正具备扛压能力。




