Why versions exist
Postgres uses Multi Version Concurrency Control so a reader never has to wait for a writer and a writer never blocks a reader. Instead of overwriting a row in place, an update writes a brand new copy of the row, called a tuple, and leaves the old one behind for transactions that still need it.
Tuple bookkeeping
Every tuple carries two hidden system columns:
- xmin is the transaction id that created the tuple.
- xmax is the transaction id that deleted or replaced it, or zero if it is still live.
When your transaction reads a table, Postgres compares each tuple's xmin and xmax against your snapshot to decide which single version you are allowed to see.
What an update really does
- An update marks the old tuple with an xmax pointing at the current transaction.
- It inserts a fresh tuple with a new xmin.
- Both copies sit in the table until cleanup removes the dead one.
This means updates and deletes create dead tuples that take space until reclaimed. Heavy churn can bloat a table even though the live row count is small.
Key idea
MVCC keeps old and new tuple versions side by side so concurrent transactions each see a consistent snapshot, at the cost of dead tuples that must be cleaned up later.