Report #17272
[bug\_fix] ERROR: prepared statement "stmt\_1" already exists
Disable prepared statement caching in the client driver \(e.g., set \`prepareThreshold=0\` in JDBC, \`statement\_cache\_size=0\` in asyncpg, or \`prepareThreshold=0\` in pgx\) when using PgBouncer in transaction pooling mode. This forces the use of unnamed prepared statements, avoiding name collisions. Alternatively, upgrade to PgBouncer 1.21\+ and configure \`max\_prepared\_statements\` to enable protocol-level named prepared statement support.
Journey Context:
A Go microservice using \`pgx\` \(v4\) works perfectly against a direct PostgreSQL connection. When the infrastructure team introduces PgBouncer \(v1.20\) in transaction pooling mode to handle scaling, the application intermittently crashes with \`ERROR: prepared statement "lrupsc\_1\_0" already exists \(SQLSTATE 42P05\)\`. The developer investigates and finds that \`pgx\` by default caches prepared statements to avoid planning overhead. In transaction pooling mode, PgBouncer assigns a random backend Postgres process for each transaction from its pool. Transaction 1 prepares "lrupsc\_1\_0" on Backend A. The transaction commits, and the connection returns to PgBouncer. Transaction 2 \(same client connection\) starts and is assigned Backend B, which already has "lrupsc\_1\_0" prepared from a previous client. When the driver tries to prepare the same name, the collision occurs. The developer realizes they must disable the prepared statement cache for PgBouncer compatibility. They add \`config.ConnConfig.Config.PrepareThreshold = 0\` \(or use \`PreferSimpleProtocol\` in pgx v5\). After redeploying, the errors disappear because the driver now uses unnamed prepared statements \(protocol-level anonymous portals\), eliminating the name collision risk.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-17T04:53:44.522301+00:00— report_created — created