PostgreSQL raises SQLSTATE 25000 (invalid_transaction_state) when a command is issued that is incompatible with the current state of the transaction. This code is the parent class for the entire Class 25 family of transaction state errors. You will rarely see bare 25000 in practice; most occurrences surface as one of its named subclasses such as 25001 (active_sql_transaction) or 25006 (read_only_sql_transaction).
What This Error Means
Class 25 (transaction_state) groups all errors related to issuing a statement at the wrong point in a transaction's lifecycle. PostgreSQL tracks a small state machine for each session: a session can be idle, inside an active transaction block, or in a failed (aborted) transaction block. Certain commands are only valid in specific states — for example, BEGIN cannot be called when already inside a transaction, and some commands like SET TRANSACTION must be issued before any other statement in the block.
When PostgreSQL detects a mismatch between the current transaction state and the requirements of the command being executed, it raises a Class 25 error, rolls back any in-progress statement (but not necessarily the whole transaction), and returns control to the client. The connection itself stays open; the transaction state after the error depends on whether the error is considered recoverable within a block.
The bare 25000 code is used as a catch-all when no more specific subclass applies. In PL/pgSQL you can catch the whole class with WHEN invalid_transaction_state THEN, which will also catch all its subclasses.
Common Causes
Issuing a
BEGINwhile already inside a transaction block. PostgreSQL issues aWARNING: there is already a transaction in progressand continues, but application code that treats this as an error may see25001(active_sql_transaction) when a command that requires an idle session (such asSET SESSION CHARACTERISTICS) is called inside a block.Calling
SET TRANSACTIONafter the first statement in a transaction.SET TRANSACTION ISOLATION LEVELandSET TRANSACTION READ ONLYmust be the very first statement afterBEGIN; calling them after any other statement raises an error in the current transaction block.Attempting a write operation on a read-only transaction. Issuing
INSERT,UPDATE,DELETE, orTRUNCATEinside a transaction that was started withBEGIN READ ONLYor on a hot-standby replica raises25006(read_only_sql_transaction).Calling certain DDL or administrative commands from within a transaction in a context that does not allow it. Some PostgreSQL extensions and certain two-phase commit operations (
PREPARE TRANSACTION) have restrictions on when they can be called relative to transaction state.Driver or ORM double-BEGIN. Connection pool implementations that eagerly call
BEGINon checkout, combined with application code that also callsBEGIN, can trigger this class of error when the pool connection is already in an active transaction.
How to Fix invalid_transaction_state
Identify the exact subclass. The five-character SQLSTATE tells you the specific subclass. Check your logs or the exception object in your driver for the full code:
-- In psql, \set VERBOSITY verbose shows the SQLSTATE \set VERBOSITY verbose BEGIN; BEGIN; -- WARNING 25001: there is already a transaction in progressFix SET TRANSACTION placement. Always call
SET TRANSACTIONas the first statement in the block:BEGIN; SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; -- correct: before any other statement SELECT ...; COMMIT;Handle read-only transaction violations. If you need to write data, ensure the transaction is not opened with
READ ONLY, and that you are connected to the primary server, not a replica:-- Wrong on a read-only transaction: BEGIN READ ONLY; INSERT INTO events (name) VALUES ('test'); -- ERROR 25006 -- Correct: open a writable transaction BEGIN; INSERT INTO events (name) VALUES ('test'); COMMIT;Audit your connection pool configuration. If your pool starts transactions automatically (common in PgBouncer session mode or some ORM setups), disable the application-level
BEGINor configure the pool to not issue one:# Example: psycopg2 — disable autocommit wrapping to manage transactions manually conn.autocommit = True # or use explicit BEGIN/COMMIT in your codeCatch the error class in PL/pgSQL when appropriate. If you are writing stored procedures, use the condition name to handle the entire class:
DO $$ BEGIN -- ... your logic EXCEPTION WHEN invalid_transaction_state THEN RAISE NOTICE 'Transaction state error: %', SQLERRM; END; $$;
Additional Information
- SQLSTATE Class 25 subclasses you are most likely to encounter in practice:
25001—active_sql_transaction: a command requiring an idle session was called inside a transaction block25002—branch_transaction_already_active25008—held_cursor_requires_same_isolation_level25P01—no_active_sql_transaction:ROLLBACKorCOMMITcalled with no open transaction25P02—in_failed_sql_transaction: a command was issued after a previous statement in the block errored; you mustROLLBACKbefore continuing25P03—idle_in_transaction_session_timeout: session timed out while sitting in an idle transaction (PostgreSQL 9.6+)25006—read_only_sql_transaction: a write was attempted in a read-only transaction or on a standby
25P02(in_failed_sql_transaction) is very common and deserves special attention: after any error inside a transaction block, PostgreSQL refuses all subsequent commands withERROR: current transaction is aborted, commands ignored until end of transaction block. The fix is always to issueROLLBACK(orROLLBACK TO SAVEPOINT) before retrying.- PostgreSQL 9.6 introduced
idle_in_transaction_session_timeout(25P03), which kills sessions that hold open transactions without activity for longer than the configured timeout. - Most PostgreSQL drivers (libpq, psycopg2, asyncpg, node-postgres) surface Class 25 errors as exceptions with the
codeorsqlstatefield set to the five-character string. Check your driver's exception hierarchy to catch subclasses selectively.
Frequently Asked Questions
Why do I see "current transaction is aborted, commands ignored until end of transaction block" after every error?
That message is 25P02 (in_failed_sql_transaction). Once any statement in a transaction block raises an error, PostgreSQL marks the transaction as aborted and rejects all further commands until you issue ROLLBACK (or ROLLBACK TO SAVEPOINT if you set a savepoint earlier). This is by design — PostgreSQL protects data consistency by not allowing you to continue in a partially-failed transaction.
Is 25000 itself ever raised directly, or is it always a subclass?
In standard PostgreSQL, you will almost always see a named subclass. The bare 25000 can appear if a third-party extension raises the parent class explicitly, or if a future PostgreSQL version adds a situation not yet covered by an existing subclass. Catching invalid_transaction_state in application code or PL/pgSQL handles both cases.
Can I ignore the WARNING: there is already a transaction in progress message from a double BEGIN?
PostgreSQL continues execution after the warning, so the application does not fail immediately. However, this usually indicates a bug in transaction management (e.g., a connection pool and application code both issuing BEGIN). The outer BEGIN is silently ignored and you are operating inside the first transaction, which may not have the isolation level or other settings you intended.
Does this error close my database connection?
No. Class 25 errors do not terminate the session or the connection. The connection remains open. Depending on the subclass, you may need to ROLLBACK the current transaction before issuing new commands, but the underlying TCP connection to PostgreSQL is unaffected.