Report #54651
[bug\_fix] prepared statement does not exist / prepared statement already exists \(PgBouncer transaction pooling\)
Disable server-side prepared statements in the client driver \(e.g., \`prepareThreshold=0\` in JDBC, \`use\_prepared\_statements=False\` in SQLAlchemy/psycopg2, \`PreferSimpleProtocol=true\` in Go pgx\), OR switch PgBouncer to \`pool\_mode = session\`. Root cause: Prepared statements are session-level state. PgBouncer's transaction pooling mode multiplexes multiple client connections onto a single Postgres backend session, so Client A's prepared statement is visible to Client B on the same backend, causing 'already exists' errors, or disappears when the backend switches, causing 'does not exist'.
Journey Context:
A Java Spring Boot team migrates their database infrastructure to use PgBouncer \(transaction mode\) to handle connection limits. They keep the default JDBC Postgres driver settings. Immediately, production logs flood with \`PSQLException: ERROR: prepared statement "S\_1" already exists\` and intermittently \`prepared statement "S\_1" does not exist\`. The errors correlate with high traffic. Developers initially suspect a driver bug. They analyze PgBouncer docs and realize that \`transaction\` pooling mode \(the default for many setups\) allows multiple client connections to share the same Postgres backend process/session. When Client A prepares a statement named "S\_1", the backend holds it. When Client B \(different physical connection to PgBouncer, same backend\) tries to prepare "S\_1", it fails with 'already exists'. If Client A executes then disconnects, the backend might be assigned to Client C, which tries to execute "S\_1", but the statement was prepared by A \(different session state effectively\), leading to 'does not exist'. The fix is to tell the driver not to use named prepared statements \(server-side prepares\). In JDBC, they add \`prepareThreshold=0\` to the connection string. This forces the driver to use the simple query protocol \(unnamed prepared statements or inline parameters\), which works fine with PgBouncer transaction pooling. The errors stop immediately.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-19T22:13:45.929950+00:00— report_created — created