Skip to content

Snapshots under the hood ​

pg_current_snapshot() exposes top-level transaction-id visibility information. Its three components are not the complete tuple-visibility algorithm: commit/abort status, own writes, command order, subtransactions, frozen tuples and row-header flags also matter.

componentmeaning
xminlowest xid still active at snapshot time; smaller xids have completed, either committed or aborted
xmaxone past the highest completed xid in the documented representation; xids at or above it had not completed when the snapshot was taken
xipin-progress top-level xids between the bounds; does not include subtransaction ids

These are Documented contracts, Table 9.85. Completion alone is not commitment: a completed transaction can be aborted. This representation is a teaching aid for the schedule, not a substitute for PostgreSQL's visibility checks.

The three numbers, live ​

A's initial ordinary read does not assign an xid; pg_current_xact_id() could assign one without a table write.

A> BEGIN;
BEGIN

A> SELECT balance FROM accounts WHERE id = 1;
 balance 
---------
     100 
(1 row)

A> SELECT pg_current_xact_id_if_assigned() AS xid;
 xid 
-----
     
(1 row)

A> UPDATE accounts SET balance = 150 WHERE id = 1;
UPDATE 1

A> SELECT pg_current_xact_id_if_assigned() AS xid;
 xid  
------
 1001 
(1 row)

B grabs the next xid and commits immediately. A is still in progress.

B> UPDATE accounts SET balance = 200 WHERE id = 2 RETURNING xmin, balance;
 xmin | balance 
------+---------
 1002 |     200 
(1 row)

C inspects its retained snapshot bounds; commit status and tuple metadata also matter for visibility.

C> BEGIN ISOLATION LEVEL REPEATABLE READ;
BEGIN

C> SELECT pg_snapshot_xmin(pg_current_snapshot()) AS xmin,
          pg_snapshot_xmax(pg_current_snapshot()) AS xmax;
 xmin | xmax 
------+------
 1001 | 1003 
(1 row)

xip = the xids that were in progress at snapshot time. A is on the list — so A is invisible even after it commits.

C> SELECT pg_snapshot_xip(pg_current_snapshot()) AS xid;
 xid  
------
 1001 
(1 row)

The arithmetic in action: B (committed, below xmax) is visible; A (in xip) is not.

C> SELECT id, owner, balance FROM accounts ORDER BY id;
 id | owner | balance 
----+-------+---------
  1 | alice |     100 
  2 | bob   |     200 
(2 rows)

A> COMMIT;
COMMIT

A has now committed — but C's snapshot already decided: still invisible.

C> SELECT id, owner, balance FROM accounts ORDER BY id;
 id | owner | balance 
----+-------+---------
  1 | alice |     100 
  2 | bob   |     200 
(2 rows)

C> COMMIT;
COMMIT

C's fresh transaction now reads A's committed value; own-write visibility is a separate rule.

C> SELECT id, owner, balance FROM accounts ORDER BY id;
 id | owner | balance 
----+-------+---------
  1 | alice |     150 
  2 | bob   |     200 
(2 rows)

Verified against PostgreSQL 18.6 · Run it yourself · Scenario source

A's ordinary read has not assigned an xid, then its UPDATE assigns one. B updates a different row and commits. C takes a Repeatable Read snapshot: it asserts A at xmin and in xip, and xmax equal to B's xid plus one. Its table reads assert A's old value 100 and B's committed 200. A commits, but C's repeat still sees the same values. After C commits, its fresh read sees A's 150.

pg_current_xact_id() can assign an xid even without a table write. Thus the observation that A's initial read had no xid is not a guarantee that read-only transactions never consume one. The transaction-id chapter documents lazy assignment and the distinction between 32-bit xid and epoch-extended xid8.

Under the isolation contract, ordinary Read Committed table reads take a new snapshot per command; Repeatable Read establishes its retained snapshot at the first non-transaction-control statement. Both can see their own earlier writes. UPDATE and locking SELECT can wait or reject changed rows under their level-specific rules. C's demonstration excludes A's newly committed version from an already retained snapshot; it does not make commit order irrelevant to which snapshot a later reader takes. No read-cost benchmark is claimed.

Further reading ​

MIT Licensed · Every transcript on this site was generated by a real database run against MySQL 8.4.11 and PostgreSQL 18.6 at 8fa27b8; shared YAML has an independent psycopg/PyMySQL check.