Download

prepared statement already exists

The session already has a prepared statement with that name. In your own SQL, DEALLOCATE it or use another name. From an application, it nearly always means a connection pooler in transaction mode is mixing different clients’ prepared statements on one server connection.

PostgreSQL error 42P05· Tested on PostgreSQL 18.6· Updated 11 October 2026

ERROR:  prepared statement "stmtcache_1" already exists

What it means

A prepared statement is a query the server has parsed and stored under a name, so it can be run again with new values. Names belong to one session (one server connection) and last until it ends. Creating a second one with a name the session already uses fails:

ERROR:  prepared statement "stmtcache_1" already exists

Its twin, prepared statement "stmtcache_2" does not exist (code 26000), is running a name the session never prepared.

Drivers prepare statements for you, with generated names (S_1, stmtcache_…, _pg3_0, a1…), and remember which ones they’ve created on their connection. Both errors mean that memory and the server disagree, and the usual reason is that the “connection” the driver holds isn’t always the same server session.

Common causes

  1. PgBouncer (or another pooler) in transaction mode. Each transaction may run on a different server connection. A driver prepares S_1 on one, and later another client whose driver also names its first statement S_1 lands on that connection: “already exists”. Or a driver runs S_1 on a connection where it was never prepared: “does not exist”.
  2. A pooler that resets sessions (DISCARD ALL when a client disconnects), so statements the driver cached are gone.
  3. Your own PREPARE run twice in the same session, such as a script executed again in an open psql or a pooled connection.
  4. A connection reused after an error in application code that tracks statement names itself.

How to fix it

For your own PREPARE

DEALLOCATE get_invoice;          -- or DEALLOCATE ALL;
PREPARE get_invoice (integer) AS SELECT id, total FROM invoices WHERE id = $1;

To see what the current session has prepared:

SELECT name, statement FROM pg_prepared_statements;

DISCARD ALL clears prepared statements along with the rest of the session state.

Behind PgBouncer in transaction mode

Choose one:

  • Let PgBouncer track them. PgBouncer 1.21 and later support protocol-level prepared statements in transaction mode when max_prepared_statements is set above zero. Statements made with SQL PREPARE still aren’t supported there.
  • Stop the driver preparing named statements. pgJDBC: prepareThreshold=0. psycopg 3: prepare_threshold=None on the connection. Other drivers and ORMs have a similar switch, often described in their notes on PgBouncer.
  • Use session mode for the clients that need prepared statements, or connect them to PostgreSQL directly.

Check you really are behind a pooler

Run SELECT pg_backend_pid(); twice, in separate transactions, on the same client connection. If the number changes, a pooler is switching server sessions under you.

Reproduce it

On PostgreSQL 18.6, with SQL PREPARE:

PREPARE get_invoice (integer) AS SELECT id, total FROM seo_err_pg.invoices WHERE id = $1;
PREPARE get_invoice (integer) AS SELECT id, total FROM seo_err_pg.invoices WHERE id = $1;
EXECUTE nope(1);
ERROR:  prepared statement "get_invoice" already exists
ERROR:  prepared statement "nope" does not exist

Driver-style, through the extended protocol: psql 18’s \parse creates a named prepared statement the way a driver does, and \bind_named runs one.

SELECT id, total FROM seo_err_pg.invoices WHERE id = $1 \parse stmtcache_1
SELECT id, total FROM seo_err_pg.invoices WHERE id = $1 \parse stmtcache_1
ERROR:  prepared statement "stmtcache_1" already exists
\bind_named stmtcache_2 1 \g
ERROR:  prepared statement "stmtcache_2" does not exist

After DEALLOCATE get_invoice, a second DEALLOCATE get_invoice gave prepared statement "get_invoice" does not exist. With \set VERBOSITY verbose, psql shows the codes: ERROR: 42P05: prepared statement "s1" already exists and ERROR: 26000: prepared statement "nope" does not exist. We didn’t run PgBouncer for this page; its behaviour is from its documentation.

In Inlet

Inlet’s Activity monitor lists the server’s sessions, so you can see how many connections your application, or the pooler in front of it, holds. When a statement fails, Inlet shows the server’s error; with your own Anthropic API key, Ask Claude (Pro, ⌘L) can fix it, sending the schema, the SQL and the error, never rows.

Inlet: a database client for the Mac

One native app for PostgreSQL, MySQL, SQL Server, SQLite, MongoDB and Redis. It explains errors where they happen, holds your edits until you save them, and keeps production read-only until you say so.

Version 0.1.0 · macOS 26 Tahoe or later · Apple silicon and Intel