When PostgreSQL raises SQLSTATE 25P02 (IN_FAILED_SQL_TRANSACTION), the error message reads:
ERROR: current transaction is aborted, commands ignored until end of transaction block
This error does not indicate a new problem — it means a previous SQL statement in the same transaction block already failed, and PostgreSQL is refusing to execute any further statements until the transaction is explicitly terminated with ROLLBACK (or ROLLBACK TO SAVEPOINT).
What This Error Means
SQLSTATE class 25 covers Invalid Transaction State errors. The specific code 25P02 is a PostgreSQL extension (indicated by the P in the subclass) rather than a standard SQL SQLSTATE code.
When a statement inside a BEGIN/COMMIT block raises an error, PostgreSQL marks the transaction as aborted. From that point on, every subsequent command in the same transaction — including otherwise-valid SELECT statements — is rejected immediately with 25P02. PostgreSQL will not execute any of them; it simply waits for the client to issue a ROLLBACK.
This design is intentional. It prevents partial, inconsistent writes from accumulating silently inside a broken transaction. The connection itself remains open and usable; only the current transaction is poisoned. Once ROLLBACK is issued, the connection returns to a normal idle-in-transaction state and new transactions can begin.
Common Causes
An earlier statement in the transaction block raised an error, and the code continued without rolling back. This is by far the most common cause. A constraint violation, a type mismatch, a division by zero, or any other runtime SQL error will abort the transaction, but application code that catches the exception and issues more queries will immediately hit
25P02.Batch or pipeline processing without error handling. When an application sends multiple SQL statements in sequence (e.g., via a pipeline, multi-statement string, or ORM batch), a failure in one statement causes all subsequent statements to fail with
25P02.Connection pool misconfiguration returning an aborted connection. If a connection pool returns a connection that was left in an aborted transaction state (e.g., after a previous user returned it without rolling back), the next query on that connection will fail immediately with
25P02.Long-lived transactions interrupted by a server-side timeout. A
statement_timeoutorlock_timeoutthat fires mid-transaction will abort the current statement and put the transaction in a failed state. Any subsequent statement from the application will receive25P02.Explicit
ROLLBACKnot issued after a deferred constraint violation. Deferred constraints are checked atCOMMITtime; when they fail, the transaction aborts and the commit statement itself errors. Subsequent retries within the same block hit25P02.
How to Fix in_failed_sql_transaction
Always
ROLLBACKafter any error inside a transaction block.BEGIN; INSERT INTO orders (id, customer_id) VALUES (1, 999); -- Suppose this fails with a foreign key violation -- Do NOT issue more statements. Roll back immediately: ROLLBACK;Use exception handling in PL/pgSQL to catch errors and roll back cleanly.
DO $$ BEGIN INSERT INTO orders (id, customer_id) VALUES (1, 999); INSERT INTO order_items (order_id, product_id) VALUES (1, 42); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'Transaction failed: %', SQLERRM; -- PL/pgSQL automatically rolls back to the block's start; -- no explicit ROLLBACK needed here inside DO blocks END; $$;Use savepoints to recover from partial failures without aborting the entire transaction.
BEGIN; INSERT INTO log_entries (msg) VALUES ('start'); SAVEPOINT before_risky; INSERT INTO orders (id, customer_id) VALUES (1, 999); -- may fail -- If the above failed: ROLLBACK TO SAVEPOINT before_risky; -- Transaction is still alive; continue with other work INSERT INTO log_entries (msg) VALUES ('order skipped'); COMMIT;In application code, check for errors after every statement and issue
ROLLBACKbefore retrying. In Python (psycopg2/psycopg3), after catching anypsycopg2.Error, callconn.rollback()before reusing the connection.import psycopg2 conn = psycopg2.connect(dsn) try: with conn.cursor() as cur: cur.execute("BEGIN") cur.execute("INSERT INTO orders ...") cur.execute("INSERT INTO order_items ...") cur.execute("COMMIT") except psycopg2.Error as e: conn.rollback() # must call before any further use of conn raiseFor connection pools, configure health checks or reset procedures. Ensure the pool resets transaction state on connection return. In PgBouncer, use
pool_mode = transactionto avoid sharing connections mid-transaction. In application-level pools (e.g., SQLAlchemy), usereset_on_return=True(the default).If you see this in a framework ORM (Django, ActiveRecord, etc.), ensure you are not swallowing database exceptions. These frameworks rely on catching exceptions and rolling back, but if your code catches and suppresses the original database error before the framework can act, the transaction will be left aborted.
Additional Information
- SQLSTATE
25P02is a PostgreSQL-specific extension in class25(Invalid Transaction State). Related codes in the same class include25000(generic invalid transaction state),25001(active SQL transaction),25006(read-only SQL transaction), and25008(held cursor requires same isolation level). - The error was present from very early versions of PostgreSQL and behavior has not changed materially across versions.
- Most PostgreSQL drivers surface this exactly as-is. JDBC drivers (pgjdbc) throw
PSQLExceptionwith SQLState25P02. Npgsql (.NET) raisesNpgsqlException. Python's psycopg2 raisespsycopg2.InFailedSqlTransaction, a direct subclass ofpsycopg2.DatabaseError. - This error itself has no performance implications — it is a guard condition that fires immediately without touching storage. The real cost is the original error that caused the transaction to abort.
- In
autocommitmode (where each statement is its own transaction),25P02cannot occur because there is no multi-statement transaction to abort.
Frequently Asked Questions
Why do I get this error on a plain SELECT when I haven't done any writes?
Once a transaction is aborted, PostgreSQL rejects every statement — reads included. The aborted state is per-transaction, not per-statement type. Issue ROLLBACK first, then re-run your SELECT in a fresh transaction.
The error message says "commands ignored" — does that mean previous statements were committed?
No. All statements issued after BEGIN are part of the same transaction block. When you ROLLBACK, everything in that block is undone, including any work done before the failing statement. Nothing is committed until COMMIT succeeds.
My connection pool returns connections that are already in an aborted state. How do I fix this?
Configure the pool to either rollback or reset connections before returning them to the pool. For PgBouncer in session mode, use server_reset_query = DISCARD ALL. For application pools, ensure aborted connections are rolled back on return. You can also detect the state by checking pg_stat_activity.state = 'idle in transaction (aborted)' and terminate those connections proactively.
Does ROLLBACK TO SAVEPOINT fix this error?
Yes, if you created a savepoint before the failing statement. ROLLBACK TO SAVEPOINT <name> restores the transaction to the savepoint's state and removes the aborted flag — you can then continue issuing statements within the same transaction. If no savepoint was created before the failure, a full ROLLBACK is required.