transaction模式能压降后端连接数,是因为它允许多个客户端短事务复用同一后端连接,前提是事务短且干净;若事务未显式提交、含临时表或监听操作等,则复用失效。

直接结论:把 pool_mode 改成 transaction 是必要但不充分的条件;真正大幅降低后端连接压力,必须同步调整 default_pool_size、禁用会话状态依赖、并确保应用事务边界清晰。
为什么 transaction 模式能压降后端连接数
PostgreSQL 的“每连接一进程”模型是瓶颈根源——每个连接至少占 10MB 内存 + 一个 OS 进程。而 transaction 模式让多个客户端请求复用同一个后端连接,前提是它们不同时处于活跃事务中。比如 1000 个并发客户端发起短事务(平均耗时 200ms),只要同一时刻只有 50 个事务在跑,后端只需维持约 50 个连接,而非 1000 个。
但这个效果有个硬前提:事务必须真正“短”且“干净”。常见破坏因素包括:
- 应用层没显式
COMMIT或ROLLBACK,导致事务长期挂起 - 用了
CREATE TEMP TABLE或SET LOCAL,这些状态无法跨连接传递,transaction模式下会被丢弃 - 执行了
LISTEN/NOTIFY,这类连接级长生命周期操作在事务模式下直接报错
配置 transaction 模式的关键参数组合
光改 pool_mode = transaction 不够,必须配套调参,否则要么压不住连接数,要么触发超时中断:
-
default_pool_size:设为 PostgreSQL 的max_connections× 0.6,向下取整。例如 PG 设置了max_connections = 200,这里就填120,不是默认的 20 -
idle_transaction_timeout:设为 45 秒,且必须比应用层事务超时(如 Spring 的@Transactional(timeout = 60))至少小 10 秒,防止 PgBouncer 先杀事务 -
server_idle_timeout:设为 30 秒,强制回收空闲超过半分钟的后端连接,避免数据库侧堆积僵尸进程 -
reserve_pool_size:设为default_pool_size的 20%,但不超过max_client_conn的 10%,给突发流量留缓冲
transaction 模式下必须检查的应用代码习惯
很多团队配完 pool_mode = transaction 就上线,结果第二天发现事务失败率飙升——问题往往出在应用层对连接行为的隐含假设上:
- 禁止在事务块外执行 DML。像
INSERT INTO t VALUES (1); COMMIT;这种单语句事务没问题,但BEGIN; INSERT ...; SELECT ...; COMMIT;中间如果混了非事务语句(如 SET 命令),PgBouncer 会把它当事务外操作,可能复用错连接 - 所有 PREPARE/EXECUTE 必须在同一个事务内完成。跨事务的
EXECUTE会失败,因为预编译计划不跨连接保留 - 临时表必须重构为普通表+唯一 session_id 字段,或改用
UNLOGGED表替代 - 监听通知类逻辑必须剥离到单独长连接通道(比如另起一个 session 模式连接池),不能和业务连接混用
验证是否真压降了后端连接数
别只看 PgBouncer 的 SHOW POOLS,重点对比两个指标:
- PostgreSQL 侧执行
SELECT count(*) FROM pg_stat_activity WHERE state = 'active' OR state = 'idle in transaction';—— 这才是真实后端压力 - PgBouncer 侧执行
SHOW CLIENTS;和SHOW POOLS;,确认cl_active远大于sv_active(比如 800 vs 60),才说明复用生效 - 如果
sv_idle长期为 0,说明default_pool_size设太小,连接池已满,新事务在排队,这时要查query_wait_time是否持续增长
最易被忽略的一点:transaction 模式下,应用日志里出现 “server closed the connection unexpectedly” 往往不是网络问题,而是某条 SQL 触发了隐式事务(如某些 ORM 的 save() 自动 BEGIN),又没及时 COMMIT,最终被 idle_transaction_timeout 强制断开。这种错误不会立刻报错,但会导致数据不一致。


















