How to fix Prepared statement does not exist in PostgreSQL

PostgreSQLINTERMEDIATEMEDIUMUpdated Dec 29, 2025· 226 views

The "Prepared statement does not exist" error occurs when using connection poolers like PgBouncer or PgPool, where prepared statements are lost between transactions or connections. This can be fixed by disabling prepared statements, using binary parameters, or connecting directly to the database.

Quick fix

Fixes Prepared statement does not exist

url=jdbc:postgresql://pgbouncer-host:6432/mydb?prepareThreshold=-1
# Or set to 0 to disable caching entirely
url=jdbc:postgresql://pgbouncer-host:6432/mydb?prepareThreshold=0
  1. Disable prepared statements in your application driver
  2. Configure PgBouncer for session-level pooling if possible
  3. Use binary parameters with the connection pooler
  4. Connect directly to PostgreSQL, bypassing the connection pooler
postgresql — error
url=jdbc:postgresql://pgbouncer-host:6432/mydb?prepareThreshold=-1
# Or set to 0 to disable caching entirely
url=jdbc:postgresql://pgbouncer-host:6432/mydb?prepareThreshold=0
Prepared statement does not exist

What this error means

This error occurs when your application tries to execute a prepared statement that no longer exists on the PostgreSQL server. Prepared statements are pre-compiled SQL queries stored on the server, identified by a unique name or ID (like S_1). When a connection pooler is used between your application and PostgreSQL, it can interfere with prepared statement management. Connection poolers like PgBouncer operate in different modes: in session mode, prepared statements persist; in transaction mode, they are deallocated after each transaction completes. When the pooler deallocates a prepared statement and your application tries to reuse it, this error occurs. The error typically manifests as "ERROR: prepared statement 'S_1' does not exist" or "pq: unnamed prepared statement does not exist" depending on your database driver. This is distinct from application-level issues where statements are simply never prepared—this error indicates the statement existed at one point but was lost due to connection pooling behavior.

Typical symptoms

  • Error message: "ERROR: prepared statement 'S_1' does not exist" or similar numbered statement
  • Error message: "pq: unnamed prepared statement does not exist" when using Go pq driver
  • Error occurs intermittently or after idle time, not on first attempt
  • Problem appears only when using PgBouncer or PgPool, not when connecting directly to PostgreSQL
  • Application works fine with direct PostgreSQL connections but fails through connection pooler
  • Java applications connecting through pgpool fail with prepared statement errors

Common causes

  1. PgBouncer running in transaction pooling mode deallocates prepared statements after each transaction, but your driver attempts to reuse them
  2. Connection pooler sends RESET ALL command when returning connections to the pool, which removes all prepared statements
  3. Application driver caches prepared statement names/IDs across multiple logical connections through the pooler
  4. Mismatch between prepared statement protocol expectations: connection pooler expects text parameters but application sends binary parameters
  5. Multiple application instances sharing the same connection pool without proper statement isolation
  6. PostgreSQL server restart or maintenance causing all prepared statements to be lost

How to fix "Prepared statement does not exist"

1Disable prepared statements in your application driver

The most reliable solution is to disable prepared statement caching in your database driver configuration.

For Java applications (JDBC):

properties
url=jdbc:postgresql://pgbouncer-host:6432/mydb?prepareThreshold=-1
# Or set to 0 to disable caching entirely
url=jdbc:postgresql://pgbouncer-host:6432/mydb?prepareThreshold=0

For Python (psycopg2):

python
import psycopg2
conn = psycopg2.connect(
    "dbname=mydb user=myuser host=pgbouncer-host port=6432",
    isolation_level=psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT
)

For Node.js (pg library):

javascript
const client = new Client({
  host: 'pgbouncer-host',
  port: 6432,
  database: 'mydb',
  statement_cache_size: 0  // Disable prepared statements
});
2Configure PgBouncer for session-level pooling if possible

If you must use prepared statements, configure PgBouncer to use session-level pooling instead of transaction-level pooling.

In your pgbouncer.ini:

ini
[databases]
mydb = host=postgres.local port=5432 dbname=mydb

[pgbouncer]
pool_mode = session  # Use session pooling instead of transaction
max_client_conn = 1000
default_pool_size = 25

Note: Session pooling uses more backend connections but preserves prepared statement state. Only use if you cannot disable prepared statements in your application.

3Use binary parameters with the connection pooler

Some drivers and poolers work better when configured to use binary parameters instead of text parameters.

For Go (pq driver):

go
import (
    "github.com/lib/pq"
)

// The pq driver should be configured to handle binary protocol correctly
db, err := sql.Open("postgres", "postgres://user:pass@pgbouncer-host:6432/mydb?sslmode=disable")

For other drivers, check if there's a configuration option to force binary parameter mode in the connection string.

4Connect directly to PostgreSQL, bypassing the connection pooler

For development or troubleshooting, connect directly to PostgreSQL to verify prepared statements work without the pooler:

bash
# Direct connection (statements work)
psql -h postgres.local -p 5432 -U myuser -d mydb

# Through PgBouncer (statements fail)
psql -h pgbouncer-host -p 6432 -U myuser -d mydb

If prepared statements work directly but fail through the pooler, the issue is definitely pooler configuration. Update your pooler settings or disable prepared statements in your driver.

5For Java applications, verify connection pool settings

If using a connection pool in your Java application (HikariCP, Tomcat JDBC Pool, etc.), ensure it's not set to transaction isolation mode when using PgBouncer.

HikariCP example:

java
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://pgbouncer-host:6432/mydb?prepareThreshold=-1");
config.setUsername("myuser");
config.setPassword("mypass");
config.setMaximumPoolSize(20);
config.setAutoCommit(true);  // Ensure autocommit is enabled
HikariDataSource ds = new HikariDataSource(config);

Advanced notes

Understanding Prepared Statement Lifecycle: When you prepare a statement in PostgreSQL, it is stored on that specific server connection with a name (S_1, S_2, etc.) or as an unnamed statement. The connection pooler's role is critical—in transaction mode, PgBouncer acts as a multiplexer, sending multiple logical transactions across fewer physical connections. When a transaction ends, PgBouncer sends RESET ALL to reset the physical connection, which deallocates all prepared statements. Your application driver, however, may cache the statement name expecting to reuse it on what it thinks is the same connection. The next transaction through the pooler gets a different physical connection or the same one after reset, and the statement no longer exists.

Prepared Statement Protocol Details: PostgreSQL supports two parameter passing methods: text and binary. Connection poolers like PgBouncer may have issues with binary protocol handling due to buffering differences. The error "caused by pgbouncer being upset over the use of plaintext parameters to prepared statements" suggests Pgbouncer has stricter validation in some configurations.

Alternatives to Prepared Statements: If disabling prepared statements causes performance concerns, consider: (1) using connection pooling directly in your application rather than PgBouncer, (2) using a different pooling strategy like PgPool with more forgiving prepared statement handling, or (3) caching query results in application memory instead of relying on server-side prepared statements. For most applications not executing thousands of queries per second, the performance difference is negligible.

Frequently asked questions

What does "Prepared statement does not exist" mean?
This error occurs when your application tries to execute a prepared statement that no longer exists on the PostgreSQL server. Prepared statements are pre-compiled SQL queries stored on the server, identified by a unique name or ID (like S_1). When a connection pooler is used between your application and PostgreSQL, it can interfere with prepared statement management.
How do I fix "Prepared statement does not exist"?
The "Prepared statement does not exist" error occurs when using connection poolers like PgBouncer or PgPool, where prepared statements are lost between transactions or connections. This can be fixed by disabling prepared statements, using binary parameters, or connecting directly to the database.
What causes "Prepared statement does not exist"?
PgBouncer running in transaction pooling mode deallocates prepared statements after each transaction, but your driver attempts to reuse them Connection pooler sends RESET ALL command when returning connections to the pool, which removes all prepared statements Application driver caches prepared statement names/IDs across multiple logical connections through the pooler
Is the "Prepared statement does not exist" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading