PostgreSQL Error 25005: No Active SQL Transaction for Branch Transaction
PostgreSQL error code 25005 (no_active_sql_transaction_for_branch_transaction) occurs when a branch transaction operation — such as a SAVEPOINT command or a distributed transaction step — is attempted without an active parent SQL transaction. This error is commonly encountered in distributed systems using Two-Phase Commit (2PC), or in application layers where connection pooling and ORM frameworks loosely manage transaction lifecycles.
Top 3 Causes
1. Using SAVEPOINT Outside a Transaction Block
The most frequent cause is calling SAVEPOINT, RELEASE SAVEPOINT, or ROLLBACK TO SAVEPOINT when autocommit is enabled and no explicit BEGIN has been issued.
-- ❌ Wrong: SAVEPOINT called without an active transaction
SAVEPOINT sp1;
INSERT INTO orders (product_id, qty) VALUES (10, 2);
-- ✅ Correct: Wrap in explicit transaction block
BEGIN;
INSERT INTO orders (product_id, qty) VALUES (10, 2);
SAVEPOINT sp1;
INSERT INTO order_details (order_id, note) VALUES (1, 'urgent');
-- On error, roll back only to savepoint
ROLLBACK TO SAVEPOINT sp1;
INSERT INTO order_details (order_id, note) VALUES (1, 'standard');
RELEASE SAVEPOINT sp1;
COMMIT;
2. Incorrect Two-Phase Commit (2PC) Sequence
In distributed environments using PREPARE TRANSACTION, issuing branch commands out of order — or after the transaction has already been finalized — triggers this error.
-- ✅ Correct 2PC flow in PostgreSQL
-- Step 1: Start transaction
BEGIN;
-- Step 2: Perform work
UPDATE accounts SET balance = balance - 500 WHERE user_id = 1;
UPDATE accounts SET balance = balance + 500 WHERE user_id = 2;
-- Step 3: Prepare (first phase of 2PC)
PREPARE TRANSACTION 'transfer_txn_42';
-- Step 4: After all participants are ready, commit
COMMIT PREPARED 'transfer_txn_42';
-- Or roll back if something fails
-- ROLLBACK PREPARED 'transfer_txn_42';
-- Check for stale prepared transactions
SELECT gid, prepared, owner FROM pg_prepared_xacts;
3. Connection Pool Returning Dirty Connections
When connection pools (PgBouncer, HikariCP) return connections without fully resetting transaction state, the next consumer may attempt SAVEPOINT operations on a connection with no active transaction context.
-- Check current transaction state before operations
SELECT pg_current_xact_id_if_assigned() AS txn_id;
-- Returns NULL if no active transaction
-- Safe connection reset (use as server_reset_query in PgBouncer)
DISCARD ALL;
-- Detect problematic idle connections
SELECT pid, usename, state, xact_start,
EXTRACT(EPOCH FROM (NOW() - xact_start)) AS idle_seconds
FROM pg_stat_activity
WHERE state = 'idle in transaction'
ORDER BY idle_seconds DESC;
Quick Fix Solutions
-- Fix 1: Always begin explicitly before using SAVEPOINT
BEGIN;
SAVEPOINT my_sp;
-- ... your work ...
RELEASE SAVEPOINT my_sp;
COMMIT;
-- Fix 2: Clean up stuck prepared transactions (DBA use)
DO $$
DECLARE r RECORD;
BEGIN
FOR r IN SELECT gid FROM pg_prepared_xacts
WHERE prepared < NOW() - INTERVAL '30 minutes'
LOOP
EXECUTE 'ROLLBACK PREPARED ' || quote_literal(r.gid);
RAISE NOTICE 'Cleaned up: %', r.gid;
END LOOP;
END;
$$;
-- Fix 3: Reset connection state safely
ROLLBACK; -- close any open transaction
RESET ALL; -- reset session parameters
Prevention Tips
1. Enforce explicit transaction boundaries.
Always use BEGIN/COMMIT/ROLLBACK explicitly in application code. Leverage framework-level transaction managers (@Transactional in Spring, with db.transaction() in SQLAlchemy) to guarantee proper lifecycle management and avoid raw SAVEPOINT calls outside transaction blocks.
2. Monitor and alert on stale transactions.
Set up regular monitoring on pg_stat_activity and pg_prepared_xacts. Configure idle_in_transaction_session_timeout in postgresql.conf to automatically terminate long-running idle transactions, reducing the risk of dirty connections being recycled by the pool.
-- postgresql.conf recommended setting
-- idle_in_transaction_session_timeout = '5min'
-- Verify current setting
SHOW idle_in_transaction_session_timeout;
Related Error Codes
| Code | Name | Description |
|---|---|---|
| 25000 | invalid_transaction_state |
Parent class for all transaction state errors |
| 25001 | active_sql_transaction |
Command not allowed inside an active transaction |
| 25006 | read_only_sql_transaction |
Write attempted in a read-only transaction |
| 25P01 | no_active_sql_transaction |
COMMIT/ROLLBACK issued with no open transaction |
| 40001 | serialization_failure |
Conflict in SERIALIZABLE isolation; requires retry logic |
📖 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)