PostgreSQL Transaction Rollback (SQLSTATE 40000)

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

  1. Serialization failure (40001): A transaction running at REPEATABLE READ or SERIALIZABLE isolation 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.

  2. 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 detected followed by a detail listing the conflicting processes and the lock each holds.

  3. Serializable isolation conflict on read-only transactions: Even transactions that only read data can be aborted under SERIALIZABLE isolation if the SSI engine detects they were part of a cycle of dependencies that would make the overall schedule non-serializable.

  4. Explicit ROLLBACK or connection loss mid-transaction: If the client connection drops or sends ROLLBACK explicitly while a transaction is in progress, the transaction rolls back. Some drivers surface this via a 40000-class exception.

  5. Statement timeout or lock timeout inside a transaction: When statement_timeout or lock_timeout fires inside an explicit transaction, the error thrown (typically 57014 or 55P03) causes the transaction to abort. Any subsequent command in that session will receive an error in the 40 class until the transaction is rolled back.

How to Fix transaction_rollback

  1. 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))
    
  2. Always issue ROLLBACK after catching a 40-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;
    
  3. 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;
    
  4. Use SAVEPOINT for 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;
    
  5. Use READ COMMITTED isolation if SERIALIZABLE is not required: Many applications use SERIALIZABLE or REPEATABLE READ unnecessarily. READ COMMITTED (the PostgreSQL default) produces far fewer 40001 errors and is sufficient for most workloads that handle concurrency at the application level.

  6. Investigate deadlocks via pg_stat_activity and logs: When 40P01 occurs, PostgreSQL logs the full deadlock detail at LOG level. Enable log_lock_waits = on and review pg_stat_activity to 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 40 has been part of PostgreSQL since very early versions. The SSI-based serialization failure detection (40001 for SERIALIZABLE isolation) was significantly improved in PostgreSQL 9.1 when true Serializable Snapshot Isolation was introduced.
  • Related SQLSTATE codes in the same class:
    • 40001serialization_failure: transaction conflicted with a concurrent transaction at REPEATABLE READ or SERIALIZABLE
    • 40002transaction_integrity_constraint_violation: rarely raised directly; reserved for integrity violations that force a rollback
    • 40P01deadlock_detected: PostgreSQL-specific code for deadlock resolution
  • Most drivers map the 40 class to a dedicated exception type. In psycopg2 it is psycopg2.extensions.TransactionRollbackError; in JDBC it maps to SQLTransactionRollbackException; in asyncpg the asyncpg.SerializationError and asyncpg.DeadlockDetectedError are 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_rollback in pg_stat_database to 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.

Subscribe to the Pulse Newsletter

Get early access to new Pulse features, insightful blogs & exclusive events , webinars, and workshops.

We use cookies to provide an optimized user experience and understand our traffic. To learn more, read our use of cookies; otherwise, please choose 'Accept Cookies' to continue using our website.