PostgreSQL Error 25P01: No Active SQL Transaction
PostgreSQL error 25P01 (no active sql transaction) occurs when you attempt to execute a transaction control command — such as ROLLBACK, COMMIT, or SAVEPOINT — without an active transaction in progress. Since PostgreSQL operates in autocommit mode by default, issuing these commands outside of an explicit BEGIN/END block results in this error. It is especially common in application code that handles exceptions poorly or in connection pool environments where transaction state is mismanaged.
Top 3 Causes
1. Calling ROLLBACK or COMMIT Without BEGIN
The most frequent cause. If no transaction has been explicitly started, PostgreSQL has nothing to roll back or commit.
-- Wrong: No BEGIN before ROLLBACK
UPDATE accounts SET balance = balance - 500 WHERE id = 1;
ROLLBACK; -- ERROR: 25P01: there is no transaction in progress
-- Correct: Wrap operations in an explicit transaction
BEGIN;
UPDATE accounts SET balance = balance - 500 WHERE id = 1;
UPDATE accounts SET balance = balance + 500 WHERE id = 2;
COMMIT;
2. Executing Commands on an Already-Terminated Transaction
After a transaction has been committed, rolled back, or aborted due to an error, attempting further transaction control commands causes 25P01.
-- Wrong: ROLLBACK after the transaction is already closed
BEGIN;
INSERT INTO orders (product_id, qty) VALUES (10, 2);
COMMIT; -- Transaction ends here
ROLLBACK; -- ERROR: 25P01 — transaction already closed
-- Correct: Use SAVEPOINT for partial rollbacks within a transaction
BEGIN;
SAVEPOINT before_insert;
INSERT INTO orders (product_id, qty) VALUES (10, 2);
-- Undo just this step if needed
ROLLBACK TO SAVEPOINT before_insert;
-- Continue or finalize
COMMIT;
3. Misconfigured Connection Poolers or ORM Transaction Management
Tools like PgBouncer, HikariCP, or ORMs (SQLAlchemy, Hibernate) can lose track of transaction state when connections are reused. If a connection is returned to the pool mid-transaction or without proper cleanup, the next request may receive a "dirty" connection leading to 25P01.
-- Check if a transaction is currently active
SELECT pg_current_xact_id_if_assigned() IS NOT NULL AS in_transaction;
-- Inspect active sessions and their transaction state
SELECT pid, state, query, now() - xact_start AS tx_duration
FROM pg_stat_activity
WHERE state IN ('idle in transaction', 'active')
ORDER BY tx_duration DESC NULLS LAST;
Quick Fix Solutions
-- If you are unsure of the current state, reset safely
-- (Only in a psql session or admin context)
DISCARD ALL; -- Resets session state including any dangling transactions
-- In PL/pgSQL, use exception blocks instead of manual ROLLBACK
DO $$
BEGIN
UPDATE inventory SET stock = stock - 5 WHERE item_id = 99;
IF NOT FOUND THEN
RAISE EXCEPTION 'Item not found';
END IF;
EXCEPTION
WHEN OTHERS THEN
RAISE WARNING 'Caught error: %', SQLERRM;
-- Do NOT call ROLLBACK here; PostgreSQL handles it automatically
END;
$$;
Prevention Tips
1. Always pair BEGIN with explicit COMMIT or ROLLBACK.
Establish a team coding standard that every data-modifying operation lives inside a proper transaction block. If using an ORM, rely on its built-in transaction context managers (e.g., with session.begin() in SQLAlchemy) rather than calling raw SQL transaction commands manually. Code reviews should flag any standalone ROLLBACK or COMMIT statements.
2. Set idle transaction timeouts and monitor pg_stat_activity.
Configure idle_in_transaction_session_timeout to automatically terminate abandoned transactions, and use your monitoring stack to alert on repeated 25P01 occurrences.
-- Automatically kill transactions idle for more than 30 seconds
ALTER SYSTEM SET idle_in_transaction_session_timeout = '30s';
SELECT pg_reload_conf();
Related Errors
- 25P02 – in_failed_sql_transaction: Opposite scenario — a transaction exists but is in an aborted state, and you attempt to run SQL without rolling back first.
-
2D000 – invalid_transaction_termination: Raised when
COMMITorROLLBACKis called in an invalid context, such as inside a function or trigger body.
📖 Want a more detailed guide?
Check out the full in-depth version (Korean) on oraerror.com — includes detailed analysis, additional SQL examples, and prevention tips.
Top comments (0)