When a transaction is forcibly aborted by PostgreSQL, you will see a message like ERROR: current transaction is aborted, commands ignored until end of transaction block or a more specific error such as ERROR: could not serialize access due to concurrent update. The SQLSTATE code is 40000 (condition name: transaction_rollback), and it represents the parent class for a family of errors that all share the same fundamental consequence: the transaction has been rolled back and cannot continue.
What This Error Means
SQLSTATE class 40 covers all transaction rollback errors in PostgreSQL. The generic 40000 code itself is rarely raised directly — it serves as the parent class for three more specific child conditions: 40001 (serialization_failure), 40002 (transaction_integrity_constraint_violation), and 40P01 (deadlock_detected). When your application or driver reports 40000, it either received the generic code or is reporting the class-level code for one of these children.
After any error in the 40 class, the transaction enters an aborted state. PostgreSQL will reject all further commands in that transaction (returning ERROR: current transaction is aborted) until the client issues a ROLLBACK (or ROLLBACK TO SAVEPOINT). Attempting to commit an aborted transaction results in an implicit rollback, not a commit.
These errors are not bugs in PostgreSQL — they are intentional signals from the concurrency control system. PostgreSQL uses Multi-Version Concurrency Control (MVCC) and, for higher isolation levels, Serializable Snapshot Isolation (SSI). When those mechanisms detect a conflict that would violate the requested isolation guarantees, they terminate the offending transaction rather than allow a consistency violation.
Common Causes
Serialization failure (40001): A transaction running at
REPEATABLE READorSERIALIZABLEisolation level encountered a concurrent modification that would produce a non-serializable outcome. PostgreSQL raises this to preserve correctness, and the application is expected to retry the transaction from scratch.Deadlock (40P01): Two or more transactions are each waiting for a lock held by the other. PostgreSQL's deadlock detector resolves this by choosing one transaction as the victim and rolling it back. The message reads
ERROR: deadlock detectedfollowed by a detail listing the conflicting processes and the lock each holds.Serializable isolation conflict on read-only transactions: Even transactions that only read data can be aborted under
SERIALIZABLEisolation if the SSI engine detects they were part of a cycle of dependencies that would make the overall schedule non-serializable.Explicit
ROLLBACKor connection loss mid-transaction: If the client connection drops or sendsROLLBACKexplicitly while a transaction is in progress, the transaction rolls back. Some drivers surface this via a40000-class exception.Statement timeout or lock timeout inside a transaction: When
statement_timeoutorlock_timeoutfires inside an explicit transaction, the error thrown (typically57014or55P03) causes the transaction to abort. Any subsequent command in that session will receive an error in the40class until the transaction is rolled back.
How to Fix transaction_rollback
Retry the transaction on
40001(serialization failure): This is the canonical, correct response. Serialization failures are transient — they do not indicate a bug. Implement exponential backoff retry logic in your application:import psycopg2 from psycopg2 import OperationalError import time def run_with_retry(conn, fn, max_retries=5): for attempt in range(max_retries): try: with conn.cursor() as cur: fn(cur) conn.commit() return except psycopg2.extensions.TransactionRollbackError as e: conn.rollback() if attempt == max_retries - 1: raise time.sleep(0.1 * (2 ** attempt))Always issue
ROLLBACKafter catching a40-class error before reusing the connection: Failing to do so leaves the connection in an aborted transaction state. All subsequent commands will fail until the transaction is ended:-- After catching the error: ROLLBACK; -- Now the connection is clean and you can start a new transaction BEGIN;Prevent deadlocks by enforcing a consistent lock acquisition order: Deadlocks most commonly occur when two transactions access the same rows or tables in opposite orders. Ensure your application always locks resources in the same sequence:
-- Always lock table A before table B in every transaction BEGIN; SELECT * FROM accounts WHERE id = 1 FOR UPDATE; SELECT * FROM accounts WHERE id = 2 FOR UPDATE; -- ... proceed COMMIT;Use
SAVEPOINTfor partial rollback recovery: If you want to recover from a sub-operation failure without rolling back the entire transaction, wrap risky operations in a savepoint:BEGIN; INSERT INTO orders (id, total) VALUES (100, 250.00); SAVEPOINT before_inventory; UPDATE inventory SET qty = qty - 1 WHERE sku = 'WIDGET'; -- If this fails with a conflict: ROLLBACK TO SAVEPOINT before_inventory; -- Handle the error, then continue or commit COMMIT;Use
READ COMMITTEDisolation ifSERIALIZABLEis not required: Many applications useSERIALIZABLEorREPEATABLE READunnecessarily.READ COMMITTED(the PostgreSQL default) produces far fewer40001errors and is sufficient for most workloads that handle concurrency at the application level.Investigate deadlocks via
pg_stat_activityand logs: When40P01occurs, PostgreSQL logs the full deadlock detail atLOGlevel. Enablelog_lock_waits = onand reviewpg_stat_activityto understand the patterns:SELECT pid, state, wait_event_type, wait_event, query FROM pg_stat_activity WHERE wait_event_type = 'Lock';
Additional Information
- SQLSTATE class
40has been part of PostgreSQL since very early versions. The SSI-based serialization failure detection (40001forSERIALIZABLEisolation) was significantly improved in PostgreSQL 9.1 when true Serializable Snapshot Isolation was introduced. - Related SQLSTATE codes in the same class:
40001—serialization_failure: transaction conflicted with a concurrent transaction atREPEATABLE READorSERIALIZABLE40002—transaction_integrity_constraint_violation: rarely raised directly; reserved for integrity violations that force a rollback40P01—deadlock_detected: PostgreSQL-specific code for deadlock resolution
- Most drivers map the
40class to a dedicated exception type. In psycopg2 it ispsycopg2.extensions.TransactionRollbackError; in JDBC it maps toSQLTransactionRollbackException; in asyncpg theasyncpg.SerializationErrorandasyncpg.DeadlockDetectedErrorare raised. Check your driver's documentation for the exact class hierarchy. - Connection poolers like PgBouncer in transaction-pooling mode interact badly with aborted transactions if the application does not call
ROLLBACK— the pooler may return the connection to the pool in a broken state. - Monitoring: track
xact_rollbackinpg_stat_databaseto observe rollback rates over time. A sustained increase is a strong signal of lock contention or serialization conflicts.
Frequently Asked Questions
Why does PostgreSQL say "current transaction is aborted" even though I caught the error?
After any error inside a transaction block, PostgreSQL marks the transaction as aborted. You must issue ROLLBACK (or ROLLBACK TO SAVEPOINT if you used one) before running any new commands. Catching the exception in your application code is not sufficient — the rollback must be sent to the server.
Should I retry on 40P01 (deadlock) the same way I retry on 40001 (serialization failure)?
Yes. Both errors are transient and retrying the transaction from scratch is the correct response. The difference is that deadlocks are often preventable by fixing lock ordering, while serialization failures under SERIALIZABLE isolation are expected and cannot always be eliminated.
Does a 40001 serialization failure mean my data is corrupt?
No. PostgreSQL aborted the transaction precisely to prevent corruption. The database remains consistent. Your transaction simply needs to be retried; when it succeeds, it will see the committed state left by the concurrent transaction and produce a correct result.
Is 40000 the same as a syntax error?
No. SQLSTATE 40000 has nothing to do with SQL syntax. Syntax errors are in the 42 class (e.g., 42601 for syntax_error). SQLSTATE 40000 means the transaction was rolled back due to a concurrency conflict or other integrity issue detected at runtime.