DEV Community

umzzil nng
umzzil nng

Posted on • Originally published at oraerror.com

PostgreSQL 25005 Error: Causes and Solutions Complete Guide

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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)