PROD欧洲主权BaaS平台打开仪表板 →

性能 · 10 最小读取值

PgBouncer 交易池解释

Affane Daylami · Fondateur · 2026年6月12日

返回博客

PgBouncer 的事务模式在每个事务结束时释放 PostgreSQL 连接,而不是在客户端断开连接时释放。这使得用几十个实际服务器连接来服务数千个 HTTP 客户端成为可能,并且它是任何短查询 REST API 的推荐模式。

该英文文本是根据法文原文自动生成的,尚未经过审查。
该页面已自动翻译。英文版具有权威性。

这种增益有一个特定的成本:事务模式默默地破坏了假设从一个请求到下一个请求的稳定 Postgres 连接的一切。会话 SET、LISTEN/NOTIFY、咨询锁、在事务中幸存的游标、命名的预准备语句。本文详细介绍了该机制,列出了这些限制及其确切症状,然后展示了生产中的后端(我们的后端,直接在其存储库中验证)如何配置它而不被它困住。有关此处引用的任何性能数据背后的测量方法,请参阅我们的 基准测试方法。

要点

  • 事务模式:服务器连接在每次事务结束时释放,而不是在客户端断开连接时释放。这是共享短 REST API 类型连接的最有效模式。
  • 构造上不兼容:会话 SET/RESET、LISTEN/NOTIFY、会话咨询锁、WITH HOLD 游标、从一个请求到另一请求重用的临时表。
  • 实践中最常见的陷阱:命名准备语句,默认情况下由多个驱动程序(sqlx、asyncpg、JDBC pgjdbc 驱动程序)激活,可以在不同的服务器连接上重播并在负载下触发类似 prepared statement does not exist 的错误。
  • 从版本 1.21 开始,PgBouncer 可以在事务模式下遵循协议准备语句(通过服务器连接进行 LRU 缓存)。如果您的应用程序在每个请求上更改 search_path,这不会阻止禁用客户端缓存。
  • 在 Aurabase 代码中验证:租户池与 statement_cache_capacity(0) 和 pool_mode=transaction中的 PgBouncer 一起运行,而 PostgREST 自愿保持直接连接,以便通过 LISTEN/NOTIFY 重新加载其架构。
#
概念

PgBouncer的3种池化模式

PgBouncer 提供了三种模式,其区别仅在于 Postgres 服务器连接何时返回公共池。官方文档将它们命名为 session、 transaction 和 statement (pgbouncer.org/features.html,“池化模式”部分,2026 年 8 月 24 日访问)。

时尚服务器连接松动会话兼容性
会话(默认)客户端断开连接时总计:SET、LISTEN、光标,一切都像现场一样工作
交易在每个事务结束时(COMMIT/ROLLBACK)部分:仅保留事务本地的内容
声明在每个单独的请求之后最小:禁止显式多查询事务

会话模式是最宽松的,但在可扩展性方面效率最低:只要客户端保持连接,Postgres 连接就会保留给客户端,即使它在两个请求之间不执行任何操作。语句模式是为非常特殊的情况(只读代理、健康检查)保留的,甚至会破坏经典的显式事务。事务模式是 REST API 实践中占主导地位的折衷方案:每个 HTTP 请求通常对应于单个短 Postgres 事务。

#
机制

事务模式如何工作,逐个连接

在事务模式下,PgBouncer 仅在客户端打开事务时将服务器连接附加到客户端,并在 COMMIT 或 ROLLBACK 时将其返回到池中。在两个事务之间,同一个客户端可能会发现自己被重新分配到完全不同的服务器连接。

具体来说,当 default_pool_size 为 20 时,PgBouncer 可以同时吸收数百个客户端,这些客户端在任何给定时间实际上只有少数交易正在进行。正是这个比率证明了具有高流量但短事务的 REST API 的事务模式是合理的:稀有资源(Postgres 连接,服务器端内存昂贵)仅在严格必要的时间内被占用。

pgbouncer.iniini
[pgbouncer]
listen_port = 5432

; La connexion serveur est libérée dès la fin de chaque transaction
pool_mode = transaction

default_pool_size = 80
max_client_conn = 1000

; Ne pas utiliser avec des sessions stateful (SET, advisory locks, LISTEN/NOTIFY)

最后一条评论总结了要点:事务模式之所以有效,是因为它故意破坏了“我的应用程序会话”和“我的 Postgres 连接”之间的链接。基于此链接的所有内容都会中断。下一节将详细列出内容。

#
限制

交易池模式有何突破

PgBouncer 官方文档明确列出了 PostgreSQL 功能,一旦服务器连接可以在来自同一客户端的两个请求之间回收,这些功能就失去了意义。

受影响的功能为什么会坏典型症状
设置/设置会话该设置适用于可以立即回收的连接两个请求之间似乎随机忘记了一个参数
聆听/通知假设有持续连接来接收通知客户永远不会收到通知,或者只是间歇性地收到通知
会话咨询锁锁由服务器连接持有,而不是逻辑客户端锁在预期完成之前释放,或者永远不会释放
带 HOLD 滑块必须在打开它的交易之后继续存在下一次迭代时出现“光标不存在”错误
临时表与 Postgres 会话相关,与事务无关该表在下一个查询中“消失”
准备好的语句名为在特定服务器连接上准备,在另一个服务器上重播负载下“准备好的语句...不存在”
陷阱并不总是立竿见影的

大多数这些限制不会在本地开发中体现出来,在本地开发中,单个连接通常服务于所有流量。当多个客户端实际上共享池并且服务器连接实际上在来自同一逻辑客户端的两个请求之间易手时,它们出现在实际负载下。烟雾测试几乎永远不会揭示它们。

#
常见陷阱

准备好的语句:最容易被误解的限制

大多数现代 Postgres 驱动程序默认在协议端准备命名请求,而无需应用程序代码显式请求它。这正是这个陷阱难以预料的原因。

协议准备语句在 Parse时命名并缓存在特定服务器连接上。在事务模式下,可以在来自同一逻辑客户端的两个请求之间将该连接重新分配给另一个客户端。如果驱动程序随后在从未准备好的连接上重播相同的语句名称,则 Postgres 会以显式错误进行响应,对于 sqlx 客户端,通常为 prepared statement "sqlx_s_N" does not exist 。该行为是间歇性的:它取决于连接在负载下的执行情况,而不取决于每次调用时可重现的确定性错误。

无论语言如何,客户端更正都是相同的:对于在事务模式下跨越池化器的任何池,禁用命名准备语句的缓存,或强制未命名查询。在带有 sqlx 的 Rust 中,它通过连接选项上的 statement_cache_capacity(0) 。

pool.rsrust
let connect_options = url
    .parse::<PgConnectOptions>()?
    .statement_cache_capacity(0);

// 等价物:asyncpg -> statements_cache_size=0, pgjdbc ->prepareThreshold=0

从版本 1.21 开始,PgBouncer 缓解了服务器端的部分问题:它可以在事务模式下遵循协议准备好的语句,并在分配的连接上动态准备它们,每个连接都有一个 LRU 缓存,其大小可通过 max_prepared_statements调整。这减少了未命中的次数,但并不能免除您在多租户池中禁用客户端缓存的情况,其中 search_path 从一个请求更改为另一个请求:缓存的计划会在 Parse时冻结已解析表的内部标识符 (OID),并且在另一个架构下重播它可能会从错误的租户返回数据,而不是简单的错误。

#
已签入代码

Aurabase 如何在事务模式下配置 PgBouncer

Aurabase 存储库将 PgBouncer 部署为共享数据平面 (deploy/helm/aurabase/templates/infra/pgbouncer.yaml) 前面的 pool_mode=transaction ,并在租户的每个专用 Postgres 实例 (deploy/cnpg/tenant-pooler.yaml) 前面部署相同配置的 Pooler CNPG。两条路径都应用上述相同的规则。

源代码记录了这种选择的特定安全原因,而不仅仅是稳定性原因。租户之间共享的 Postgres 池在重用连接上为每个项目放置不同的 search_path 。缓存的准备好的语句会在 Parse时冻结已解析表的 OID;在同一连接上为另一个租户重播它会针对第一个租户的架构运行查询,这是一种隔离绕过,而不仅仅是应用程序错误。 因此,毫无例外地应用 statement_cache_capacity(0),包括在事务模式下也通过 CNPG Pooler 的专用实例上。

第二个会话卫生措施:当每个连接返回池时,挂钩执行 DISCARD ALL (重置设置、在服务器端取消分配准备好的语句、释放咨询锁、清除游标和临时表)。如果没有此钩子,先前请求所造成的会话残留可能会在来自不同租户的下一个请求中泄漏,从而重用相同的回收连接。

Exception accepted: dedicated PostgREST instances remain in direct connection to the primary, without going through the pooler. PostgREST schema reloading relies on LISTEN/NOTIFY, which assumes a persistent connection, exactly the functionality that transaction mode breaks (detail already documented in our article on PostgREST compatibility at Aurabase). RLS settings per request are passed to SET LOCAL within an explicit transaction, the only way to remain compatible with a pool that can change server connections at any COMMIT (see our article onmulti-tenant RLS isolation).

#
实用指南

启用事务模式而不破坏您的应用程序

一个简短的清单,适用于在事务模式下从直接 Postgres 连接转移到 PgBouncer 的任何后端。

  1. 审核应用程序代码。 查找非事务 SET、 LISTEN/NOTIFY、会话咨询锁、 WITH HOLD 游标和查询之间重用的临时表。
  2. 在显式事务中将会话集替换为本地 集。这是唯一能够在连接回收后正常存在的设​​置,因为它在 COMMIT/ROLLBACK 时被清除,而不是在下一个连接时泄漏。
  3. 如果您的池遍历池程序并且模式或角色从一个请求更改为另一请求,则 禁用驱动程序端准备好的语句缓存。性能成本是真实但可衡量的,并且远低于租户之间泄漏的风险。
  4. 将真正需要 会话模式(迁移、管理脚本、任何依赖于 LISTEN/NOTIFY 的内容)的连接隔离到直接非池化连接,而不是放弃所有其他流量的事务模式。
  5. 大小 default_pool_size 和 max_client_conn 相对于 Postgres 的实际 max_connections,而不是从另一个项目复制的任意数字。
  6. 在实际负载下进行测试,而不仅仅是冒烟测试。 准备好的语句错误和会话设置泄漏几乎不会出现在单个本地连接上。
  7. 一旦投入生产,就从 PgBouncer 管理控制台监控 SHOW POOLS 和 SHOW STATS,以便在客户端可见之前发现池饱和情况。
#
决定

您应该始终选择事务模式而不是会话模式吗?

不,但它是绝大多数 REST API 的正确默认选择。对于严重依赖于无法快速重构的会话功能的遗留应用程序,或者对于池增益无法补偿迁移工作的低流量,会话模式仍然是首选。

PgBouncer 也不是该池模型的唯一实现:Supavisor (Supabase) 和 PgCat 是最近的两个替代方案,在负载分配和集群方面有不同的权衡。请参阅我们的详细比较 PgBouncer vs Supavisor vs PgCat,根据您的拓扑在三者之间进行选择。

#
常见问题解答

常见问题解答

在生产中激活交易模式后最常出现的问题。

什么是PgBouncer交易池模式?+
这是 PgBouncer 的 3 种模式之一(带有会话和语句),其中 Postgres 连接在每个事务结束时(而不是在客户端断开连接时)重新分配给另一个客户端。这使得可以为比实际打开的 Postgres 连接更多的竞争客户端提供服务。
为什么我的准备好的语句在事务模式下崩溃?+
命名准备语句是在特定服务器连接上准备的。在事务模式下,可以在两个请求之间将该连接重新分配给另一个客户端。如果您的驱动程序在从未准备过的连接上重放语句名称,Postgres 将返回一个错误,例如准备好的语句不存在。更正包括禁用驱动程序端的准备好的语句缓存(sqlx 的statement_cache_capacity(0),asyncpg 的statement_cache_size=0)。
我们可以在事务模式下在 PgBouncer 后面使用 LISTEN/NOTIFY 吗?+
不,不可靠。 LISTEN/NOTIFY 假设使用持久连接来接收通知,但事务模式不保证这一点。标准做法是通过直接连接在池化器外部将依赖于 LISTEN/NOTIFY(例如 PostgREST)的组件传递到 Postgres。
我们应该在事务模式下使用 SET LOCAL 而不是 SET 吗?+
是的,系统地适用于必须适用于给定请求的任何设置。 SET LOCAL 在 COMMIT 或 ROLLBACK 时自动清除,从而确保服务器连接在两个事务之间可能发生变化时的安全性。经典的 SET 可能会泄漏到下一个客户端,该客户端恢复相同的回收服务器连接。
事务模式是否与行级安全性 (RLS) 一起使用?+
是的,前提是 RLS 策略使用的 JWT 声明或会话变量是在事务内的 LOCAL SET 中设置的,而不是在会话 SET 中设置的。这是我们有关多租户 RLS 隔离的文章中描述的模式。
PgBouncer、Supavisor、PgCat:选择哪一个?+
三者实现了类似的池化模型,但在集群、负载分配和生态系统方面存在差异(Supavisor 是由 Supabase 开发的,PgCat 是用 Rust 编写的)。选择首先取决于您的部署拓扑和现有的操作限制:有关详细信息,请参阅我们的专门比较。

准备好部署了吗?

五分钟内完成您的后端。

无需信用卡 · 500 MB 免费 · 50,000 MAU