PgBouncer 实战:PostgreSQL 连接池调优

一、为什么需要 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_connPgBouncer 接受的最大客户端连接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 × 库数 不超过 PG max_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 基础能力 与 逻辑复制 等高可用手段,你的数据库才真正具备扛压能力。

上一篇 Java 单例模式实战:DCL、静态内部类与枚举的安全写法
下一篇 systemd 服务管理实战:开机自启与排障
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]