内容提要
文章主要讨论PostgreSQL连接池被“污染”的问题。当PgBouncer在事务模式下复用连接时,若客户端遗留了如`default_transaction_read_only`等会话状态,会导致后续查询失败(错误码25006)。解决方法包括执行`DISCARD ALL`重置连接,修复应用代码,并建议将读流量路由到副本以避免污染主库连接池。
延伸解读
连接池污染的本质
连接池污染源于PgBouncer在事务模式下复用底层连接时,客户端遗留的会话状态(如`default_transaction_read_only`)被后续请求继承。这种状态泄漏会导致后续写入操作报错25006,但错误信息与数据库只读模式(如磁盘满)不同,需注意区分。
快速恢复与代码修复
恢复被污染连接池的快速方法是执行`DISCARD ALL`重置所有连接状态,但需对所有连接执行,因为难以定位具体问题连接。根本解决需修复应用代码,避免在事务外设置会话级只读变量,并确保事务有严格超时或使用副本路由读流量。
预防策略:副本路由优先
防止只读污染的最简单方案是将读流量路由到副本,而非在主库上强制只读事务。若无法使用副本,应避免通过PgBouncer设置`default_transaction_read_only`等会话变量。使用支持副本路由的ORM(如Drizzle)可简化重构,降低风险。
Q&A
什么是Postgres连接池污染?
连接池污染是指当PgBouncer在事务模式下复用连接时,前一个客户端遗留的会话状态(如default_transaction_read_only = on)影响了后续客户端的查询,导致后续查询失败。
Postgres连接池污染会导致什么错误?
会导致Postgres错误码25006,并出现类似“ERROR: cannot execute INSERT in a read-only transaction”的错误。
如何快速修复被污染的Postgres连接池?
执行DISCARD ALL来重置连接状态,并且需要对连接池持有的每个连接都执行,因为很难确定哪个连接有问题。
如何防止Postgres连接池被污染?
将读流量路由到副本,避免在主库上强制只读;如果无法使用副本,确保事务有严格超时,并且不要设置default_transaction_read_only等会话变量。
PgBouncer在事务模式下如何复用连接?
PgBouncer在事务模式下,每个底层连接可以被多个客户端复用,从而允许1000个客户端仅使用20-50个直接连接。
如何区分连接池污染和数据库只读模式?
连接池污染的错误是“cannot execute INSERT in a read-only transaction”(错误码25006),而数据库只读模式(如磁盘满)会显示“pg_readonly: invalid statement because cluster is read-only”。
使用PlanetScale MCP如何帮助修复连接池污染?
PlanetScale MCP服务器可以查看查询洞察、错误,并生成临时连接字符串来检查卡住的会话变量,结合代码库上下文,代理可以快速定位并修复导致会话状态意外修改的代码。