Postgres lets many transactions read and write at the same time without a global lock, and it stays correct. The trick is MVCC — Multi-Version Concurrency Control — plus a choice of isolation level that decides how much of other transactions' work you're allowed to see.
The seed is the familiar accounts ledger: three owners, 100 each. We'll use it to see the row versions Postgres keeps under the hood, then reason about what concurrent transactions observe.
Rows carry hidden version columns
Every row has system columns you don't normally select. Two of them, xmin and xmax, track which transaction created this row version and which transaction deleted it (0 means "still live"). Ask for them explicitly:
SELECT xmin, xmax, id, owner, balance FROM accounts ORDER BY id;
Each row shows the xmin of the transaction that inserted it and xmax = 0 — nobody has superseded these versions yet.
An UPDATE writes a new version, it doesn't overwrite
This is the heart of MVCC: UPDATE never edits a row in place. It marks the old version as expired (sets its xmax) and writes a brand-new version with a fresh xmin. Update Ada, then look again:
UPDATE accounts SET balance = balance + 10 WHERE owner = 'ada';
SELECT xmin, xmax, id, owner, balance FROM accounts ORDER BY id;
Ada's row now has a different xmin than grace and linus — it's a new version, stamped by the transaction that just ran. The old version still physically exists on disk with its xmax set, invisible to new queries, until VACUUM reclaims it later. That's why writers don't block readers: an old reader can keep seeing the old version while a writer lays down a new one.
Every transaction reads from a snapshot
When a transaction takes its snapshot, it freezes a consistent point-in-time view: it sees row versions committed before that instant, and ignores versions from transactions still in flight. Each transaction also has an ID you can read: