PostgreSQL supports the SQL-standard two-phase commit protocol through PREPARE TRANSACTION and COMMIT PREPARED / ROLLBACK PREPARED. This allows a transaction to be durably prepared (surviving crashes) and then committed or rolled back in a separate session – a building block for distributed transactions coordinated by an external transaction manager.
| File | Purpose |
|---|---|
src/backend/access/transam/twophase.c |
Core two-phase commit implementation |
src/include/access/twophase.h |
GlobalTransaction, API |
src/include/access/twophase_rmgr.h |
Callbacks for resource managers |
src/backend/access/transam/xact.c |
PrepareTransaction(), integration with xact lifecycle |
Normal transactions have a simple lifecycle: BEGIN, do work, COMMIT (flush WAL, set CLOG). Two-phase commit splits this into two durable steps:
PREPARE: All work is complete. Locks are held. The transaction state is serialized to a WAL record and a state file on disk. The originating backend disconnects from the transaction.
COMMIT PREPARED (or ROLLBACK PREPARED): A different session (or the same one after reconnecting) finalizes the transaction.
Between PREPARE and COMMIT PREPARED, the transaction holds all its locks, is visible in pg_prepared_xacts, and survives server crashes. This is fundamentally different from a normal uncommitted transaction, which is rolled back on crash.
sequenceDiagram
participant App as Application
participant PG1 as Backend 1
participant PG2 as Backend 2
participant WAL as WAL
participant Disk as pg_twophase/
App->>PG1: BEGIN
App->>PG1: INSERT / UPDATE ...
App->>PG1: PREPARE TRANSACTION 'gid_123'
PG1->>WAL: Write prepare WAL record
PG1->>Disk: Write state file
PG1->>PG1: Release backend, keep locks via dummy PGPROC
Note over PG1: Backend 1 is now free
App->>PG2: COMMIT PREPARED 'gid_123'
PG2->>WAL: Write commit-prepared WAL record
PG2->>Disk: Remove state file
PG2->>PG2: Release locks, set CLOG to COMMITTED
Each prepared transaction is represented by a GlobalTransactionData struct (opaque outside twophase.c):
typedef struct GlobalTransactionData
{
GlobalTransaction next; /* list link */
int pgprocno; /* dummy PGPROC index */
TimestampTz prepared_at; /* time of PREPARE */
XLogRecPtr prepare_start_lsn; /* WAL position of prepare record */
XLogRecPtr prepare_end_lsn;
Oid owner; /* user who prepared */
Oid databaseId;
FullTransactionId fxid;
char gid[GIDSIZE]; /* the global transaction identifier */
} GlobalTransactionData;
When a transaction is prepared, it releases its backend’s PGPROC but acquires a dummy PGPROC from a reserved pool. This dummy PGPROC:
pid = 0 (distinguishing it from real backends)xid set to the prepared transaction’s XID, so TransactionIdIsInProgress() returns trueThe reserved pool is sized by max_prepared_transactions (default 0, meaning prepared transactions are disabled).
PrepareTransaction() in xact.c performs these steps:
pg_twophase/ for faster access during recovery (the file is named by the XID in hex)FinishPreparedTransaction(gid, isCommit) handles both cases:
pg_twophase/ filePrepared transactions survive crashes. During recovery:
PrescanPreparedTransactions(): Scans pg_twophase/ state files and the WAL for prepare records. Returns the oldest XID among prepared transactions.StandbyRecoverPreparedTransactions(): Restores dummy PGPROCs in standby mode.RecoverPreparedTransactions(): Fully restores prepared transactions including locks and resource manager state. Called after WAL replay is complete.After recovery, the prepared transactions appear in pg_prepared_xacts exactly as they did before the crash, and an administrator or application can COMMIT PREPARED or ROLLBACK PREPARED them.
SELECT * FROM pg_prepared_xacts;
Returns:
| Column | Type | Description |
|---|---|---|
transaction |
xid | Transaction ID |
gid |
text | Global transaction identifier |
prepared |
timestamptz | When PREPARE was executed |
owner |
name | User who prepared |
database |
name | Database |
The GID is a string (up to GIDSIZE = 200 bytes) that uniquely identifies the prepared transaction across the cluster. It is chosen by the application or transaction manager:
PREPARE TRANSACTION 'order_12345_payment';
-- later, possibly from a different session:
COMMIT PREPARED 'order_12345_payment';
For logical replication, PostgreSQL generates GIDs automatically using the subscription OID and transaction ID.
Logical replication uses two-phase commit to apply transactions atomically on the subscriber. The TwoPhaseTransactionGid() function generates deterministic GIDs for this purpose, and LookupGXactBySubid() checks whether a subscription’s prepared transaction already exists.
| Structure | Location | Role |
|---|---|---|
GlobalTransactionData |
twophase.c |
State for each prepared transaction |
TwoPhaseFileHeader |
twophase.c |
Header of the serialized state file |
PGPROC (dummy) |
proc.h |
Holds locks and ProcArray slot for prepared xact |
TwoPhaseRmgrId |
twophase_rmgr.h |
Resource manager callbacks for state serialization |
IN_PROGRESS in CLOG until COMMIT PREPARED sets them to COMMITTED. See CLOG and Subtransactions.