MVCC (Multi-Version Concurrency Control)
MVCC is the foundational architectural mechanism PostgreSQL uses to let many transactions read and write the same data concurrently, without readers blocking writers or writers blocking readers. It underpins essentially everything about how PostgreSQL behaves under concurrent load.
The core idea: snapshots, not locks
Instead of locking rows to keep concurrent transactions from interfering with each other, PostgreSQL gives every transaction a consistent snapshot of the data as of roughly when it started. When a row is updated, PostgreSQL doesn't overwrite it in place — it creates a new version of the row and leaves the old version intact, marked as superseded. Each transaction's snapshot determines which version of a row it is allowed to see.
Conceptually, an UPDATE creates a new row version
SQL
-- Before: one row version, visible to everyone -- id=1, price=10.00 (created by transaction 100) UPDATE products SET price = 12.00 WHERE id = 1; -- run by transaction 105 -- After: two row versions exist on disk -- id=1, price=10.00 (created by tx 100, superseded by tx 105) -- id=1, price=12.00 (created by tx 105) -- -- A transaction whose snapshot started before tx 105 committed still -- sees price=10.00. A transaction started after sees price=12.00.
Why SELECT never blocks on concurrent writes
Because a reader is simply looking at whichever row versions its own snapshot says are visible, it never needs to wait for a writer to finish, and a writer never needs to wait for readers to finish either — they're working with different versions of the row rather than contending over the same physical copy. A long-running report query and a stream of concurrent updates to the same table simply don't block each other. This is a genuine, practical advantage: plain
SELECT statements in PostgreSQL never need to take a read lock at all.Note
This is why PostgreSQL doesn't need read locks for ordinary
SELECT statements — a real advantage over traditional lock-based concurrency models, where a reader can be forced to wait for a writer (or vice versa) simply to maintain consistency. MVCC sidesteps that entire class of contention by design.The trade-off: dead row versions
Nothing is free. Once no transaction's snapshot can possibly need an old row version anymore, that version is dead — it's just wasted space sitting in the table until something cleans it up. PostgreSQL doesn't delete these dead versions immediately or automatically as part of every transaction; they accumulate and need to be reclaimed separately.
Tip
Reclaiming those dead row versions is the job of VACUUM — PostgreSQL's built-in maintenance process, normally run automatically by autovacuum. It's the direct counterpart to MVCC: MVCC creates new row versions instead of locking, and vacuum is what cleans up the old ones it leaves behind. That process is covered in depth on its own page.
MVCC lets transactions read and write concurrently without readers and writers blocking each other.
Each transaction sees a consistent snapshot of the data as of when it started.
Updates create new row versions rather than overwriting rows in place.
A direct benefit: ordinary
SELECTstatements never need to take a read lock.The trade-off is accumulating dead row versions, which
VACUUMis responsible for reclaiming.