受污染的Postgres连接池
摘要
本文解释了在使用PgBouncer时PostgreSQL中受污染的连接池问题,其中过时的会话状态可能导致写入错误,并提供了一种使用DISCARD ALL来重置连接的解决方案。
<p><a href="https://lobste.rs/s/d52jy6/poisoned_postgres_connection_pools">评论</a></p>
查看缓存全文
缓存时间: 2026/08/20 06:40
# 被污染的PostgreSQL连接池 — PlanetScale
来源:https://planetscale.com/blog/postgres-poisoned-connection-pools
你知道连接池可能会被"污染"吗?
大多数人对此毫无概念,但如果连接池管理不当,它可能导致整个数据库宕机。工程师最噩梦般的场景,莫过于早上醒来发现数据库疑似处于只读状态,却找不到明显故障点。
不幸的是,上周二傍晚,我们的一位Discord好友就遭遇了这种情况。
在PlanetScale,我们已协助众多客户在各种编程语言、ORM和应用平台上调试此问题。这些问题的核心,都源于与连接池的复杂交互。
## 什么是连接池(https://planetscale.com/blog/postgres-poisoned-connection-pools#what-is-a-connection-pool)
连接池使PostgreSQL能够扩展到数千个并发连接,而不会因过多的查询处理后端而过载。
PgBouncer(https://planetscale.com/docs/postgres/connecting/pgbouncer)是最常用的PostgreSQL连接池,也是驱动PlanetScale PostgreSQL集群的核心组件。PgBouncer维护多个到数据库的连接,并通过多路复用技术处理客户端连接。这就是为何1000个客户端能连接到一个PostgreSQL实例,而底层仅需20-50个直接连接。
## 什么是被污染的连接池(https://planetscale.com/blog/postgres-poisoned-connection-pools#what-are-poisoned-connection-pools)
当PgBouncer运行在事务模式下(这是大多数应用最实用的模式,也是PlanetScale集群的默认设置)时,每个底层连接可在多个客户端间复用。这正是它们能通过PgBouncer使用较少直接连接的关键。
每个新客户端在查询数据库的生命周期内,都有能力设置会话属性。当先前的客户端以不理想状态离开池时,可能会影响后续客户端查询的正常执行,使该连接池因残留状态而被"污染"。
这种残留状态常会导致后续查询出现严重故障。若查询设置了`default_transaction_read_only = on`或应用了会话特性(如`SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY`),底层连接将永久保持该状态。
我协助过的一位客户就遇到了后一种情况:他们的PgBouncer连接因会话特性设置而卡在只读状态。他们API的某个路由为只读事务设置了`SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY`,无意中将整个会话永久强制为只读模式——即使事务外也受影响。
此后每当其他API请求命中该路由时,PgBouncer都会重用那个仍处于只读状态的底层连接,导致大量错误爆发。
如果你曾遇到PostgreSQL错误代码`25006`,或看到如下写入失败错误,很可能就是连接池被污染了:
``
ERROR: cannot execute INSERT in a read-only transaction
``
注意
被污染的连接池产生的错误与只读数据库集群不同。若数据库因磁盘空间问题进入只读模式,会看到以下错误:
``
pg_readonly: invalid statement because cluster is read-only
```
此错误不仅可能来自被污染的连接池,也可能是向副本发送写入查询时返回。若怀疑连接池被污染,首先应确认错误是否来自副本。
通过PlanetScale Insights的错误标签页,你可以查看主库或副本产生的每类SQL错误详情。
## 解毒方案(https://planetscale.com/blog/postgres-poisoned-connection-pools#the-antidote-to-poisoned-pools)
恢复被污染连接池的快速方法是执行`DISCARD ALL`。该命令会将连接状态重置为默认值,清除`default_transaction_read_only = on`及其他所有会话设置。
此修复需应用于池中所有连接,因为很难定位具体哪个连接出了问题。可通过脚本并行调用尽可能多的连接,或使用`pscale`命令行工具实现。
执行`pscale branch connections top [database] [branch]`可查看所有活跃会话,从而终止可疑的被污染会话。
下一步是修复导致状态泄漏的应用代码。
### 使用PlanetScale MCP快速修复(https://planetscale.com/blog/postgres-poisoned-connection-pools#quick-fix-with-planetscales-mcp)
PlanetScale MCP(https://planetscale.com/docs/connect/mcp)服务器可查看查询洞察、错误信息,甚至能生成临时连接字符串以检测会话变量卡滞。结合PlanetScale MCP与代码库上下文,智能体可快速定位意外修改会话状态的代码位置。
若连接池被污染,可设置PlanetScale MCP服务器,并使用以下提示词指导智能体定位修复可疑源头:
``
使用PlanetScale MCP服务器和此代码仓库,查找并修复卡在只读模式的连接池(错误25006 / cannot execute INSERT in a read-only transaction)。
搜索会话级SET default_transaction_read_only、未提交的BEGIN事务,以及跳过ROLLBACK的超时路径。
优先使用带严格超时的事务级只读模式,或副本连接。
使用ROLLBACK或DISCARD ALL重置连接。
```
你也可以让智能体参考PlanetScale关于只读问题(https://planetscale.com/docs/postgres/troubleshooting/read-only-mode#read-only-transactions-vs-cluster-read-only-mode)的文档获取更多背景信息。
## 预防只读连接池污染(https://planetscale.com/blog/postgres-poisoned-connection-pools#preventing-read-only-poisoned-pools)
为预防只读连接池污染,最简单的方案是将读流量导向副本,而非在主库事务中强制只读。当无法使用副本时,请确保所有事务都有严格超时,且在通过PgBouncer连接数据库时不应设置`default_transaction_read_only`等会话变量。
重构代码库可能令人望而生畏,但Drizzle等ORM通过副本路由(https://orm.drizzle.team/docs/read-replicas)功能可简化此过程。结合PlanetScale MCP,保护应用免受连接池污染可能仅需一下午的工作,而非耗时一个月的重构。
若想保持连接池健康并确保应用持续在线,请安装我们的MCP服务器(https://planetscale.com/docs/connect/mcp),立即检测是否存在查询污染风险。
相似文章
有人在不使用 PgBouncer 的情况下运行 Postgres 吗?
一位开发者回顾了一篇十年前的关于 Postgres 连接管理的文章,调查了流行的托管 Postgres 提供商,并认为 PgBouncer 式的连接池实际上是一个强制性的核心功能。
PgBouncer 工作原理
一份详细的技术指南,解释 PgBouncer 作为 PostgreSQL 连接池的工作原理,涵盖其连接池模式、生产部署及常见陷阱。
我们为什么又构建了一个Postgres连接池
PgDog是一个新的Postgres连接池,它通过处理SET语句和LISTEN/NOTIFY来保留应用程序代码,与需要权衡的现有连接池不同。
PgDog
PgDog 是一款无需修改应用程序即可扩展 PostgreSQL 的工具,提供连接池和负载均衡功能。
我们将PgBouncer扩展到4倍吞吐量
ClickHouse Managed Postgres通过运行一组使用SO_REUSEPORT的进程,将PgBouncer扩展到4倍吞吐量,实现了多核利用率,并通过对等连接解决了取消转发问题。