PostgreSQL Error 25004: Inappropriate Isolation Level for Branch Transaction
PostgreSQL error code 25004 (INAPPROPRIATE_ISOLATION_LEVEL_FOR_BRANCH_TRANSACTION) occurs when a branch transaction in a distributed transaction environment attempts to use an isolation level other than SERIALIZABLE. This error is most commonly encountered in Two-Phase Commit (2PC) workflows and XA transaction manager integrations. If your middleware or application sets a lower isolation level before calling PREPARE TRANSACTION, PostgreSQL will immediately reject it with this error.
Top 3 Causes
1. Using PREPARE TRANSACTION Without SERIALIZABLE Isolation
PostgreSQL only permits SERIALIZABLE isolation for branch transactions participating in 2PC. Running PREPARE TRANSACTION while in READ COMMITTED or REPEATABLE READ mode will trigger error 25004.
-- ❌ Fails: default isolation level is READ COMMITTED
BEGIN;
INSERT INTO orders (customer_id, amount) VALUES (10, 250.00);
PREPARE TRANSACTION 'txn_001'; -- ERROR 25004!
-- ✅ Correct: explicitly set SERIALIZABLE
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
INSERT INTO orders (customer_id, amount) VALUES (10, 250.00);
PREPARE TRANSACTION 'txn_001'; -- OK
-- Commit or rollback the prepared transaction
COMMIT PREPARED 'txn_001';
2. XA DataSource Configured With Wrong Isolation Level
In Java EE / Jakarta EE environments using XA datasources (Atomikos, JBoss, WebLogic), the defaultTransactionIsolation property is often set to TRANSACTION_READ_COMMITTED. When the XA transaction manager attempts to prepare a branch, PostgreSQL rejects the non-serializable isolation level.
-- Check the current session isolation level in PostgreSQL
SHOW transaction_isolation;
-- Force SERIALIZABLE at the session level as a workaround
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- Now the XA branch can be safely prepared
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
UPDATE accounts SET balance = balance - 500 WHERE id = 7;
PREPARE TRANSACTION 'xa_branch_transfer_007';
COMMIT PREPARED 'xa_branch_transfer_007';
3. Server or Session Default Isolation Level Changed
A DBA or developer may have altered the default isolation level at the server (postgresql.conf) or session level, unknowingly breaking all 2PC workflows that rely on the serializable default.
-- Check the server-wide default
SELECT name, setting, source
FROM pg_settings
WHERE name = 'default_transaction_isolation';
-- Check for stuck prepared transactions caused by the misconfiguration
SELECT gid, prepared, owner, database,
EXTRACT(EPOCH FROM (NOW() - prepared)) AS pending_seconds
FROM pg_prepared_xacts
ORDER BY prepared;
-- Clean up orphaned prepared transactions
ROLLBACK PREPARED 'txn_orphan_stale';
Fix in postgresql.conf:
default_transaction_isolation = 'serializable'
Then reload without a full restart:
SELECT pg_reload_conf();
SHOW default_transaction_isolation; -- should return: serializable
Quick Fix Summary
| Cause | Fix |
|---|---|
Wrong isolation in BEGIN
|
Use BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE
|
| XA datasource config | Set defaultTransactionIsolation=SERIALIZABLE in datasource |
postgresql.conf default |
Set default_transaction_isolation = 'serializable' and reload |
Prevention Tips
Enforce isolation level in code: Always explicitly declare ISOLATION LEVEL SERIALIZABLE when starting any transaction that involves PREPARE TRANSACTION. Never rely on the server default for distributed transaction workflows.
Monitor pg_prepared_xacts regularly: Orphaned prepared transactions block VACUUM, cause lock contention, and bloat storage. Use the query below as a scheduled health check or monitoring alert:
-- Alert if any prepared transaction is pending for more than 10 minutes
SELECT gid, prepared, owner,
EXTRACT(EPOCH FROM (NOW() - prepared)) AS pending_seconds
FROM pg_prepared_xacts
WHERE prepared < NOW() - INTERVAL '10 minutes';
Related errors to watch: 25000 (Invalid Transaction State — parent class), 25006 (Read Only SQL Transaction), and 40001 (Serialization Failure — which may appear after switching to SERIALIZABLE and requires proper retry logic in your application).
📖 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)