Report #66648
[bug\_fix] Applications using prepared statements fail with 'prepared statement does not exist' when running through PgBouncer in transaction pooling mode
Disable prepared statements in the application client configuration \(e.g., 'prepareThreshold=0' in JDBC, 'statement\_cache\_size=0' in asyncpg, or 'PREPARE\_THRESHOLD=0' in psycopg\). Alternatively, switch PgBouncer to session pooling mode, though this reduces the scalability benefits of pooling.
Journey Context:
A Java developer switches their Spring Boot application from connecting directly to PostgreSQL to using PgBouncer \(in transaction mode\) to handle higher connection counts. Immediately, they start seeing 'PSQLException: ERROR: prepared statement "S\_1" does not exist' intermittently, causing 500 errors. Investigating, they learn that PostgreSQL prepared statements \(named protocol-level prepared statements\) are stored in the backend process's memory and are session-specific. PgBouncer in transaction mode assigns different backend PostgreSQL processes to each transaction, so a prepared statement created in Transaction A \(backend 1\) doesn't exist in Transaction B \(backend 2\). The developer initially considers switching PgBouncer to session mode, but this limits the pool to one transaction per connection, reducing the benefits. Instead, they configure the JDBC driver to disable prepared statement caching by setting 'prepareThreshold=0' in the connection string \('jdbc:postgresql://host/db?prepareThreshold=0'\). This forces the driver to use the simple query protocol or unnamed prepared statements, which are not session-persistent and work correctly with PgBouncer's transaction pooling.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-20T18:20:52.223747+00:00— report_created — created