MVCC (Multi-Version Concurrency Control)
In plain English
Plain definition
Instead of overwriting data, MVCC keeps old versions around and tags each with a timestamp or transaction ID. Readers pick the newest version that existed when their transaction started, so they never have to wait for writers, and writers never have to wait for readers.
MVCC decouples readers from writers by never mutating a record in place. Instead, every write creates a new version tagged with a monotonically increasing identifier — typically a transaction ID, a commit timestamp, or an HLC value in geo-distributed systems. A read operation is bound to a snapshot (a specific timestamp or transaction epoch) and simply scans backward through the version chain until it finds the newest version whose commit timestamp is <= the snapshot’s timestamp. This is why MVCC readers never acquire row-level locks: they are reading immutable history, not contending for the current mutable state.
The architectural cost is version bloat. Every UPDATE or DELETE is logically an INSERT of a new version plus a marker on the old one (conceptually a tombstone, though the garbage collection mechanics differ from distributed tombstone propagation). Systems must run a reclamation process — Postgres calls it VACUUM, CockroachDB and Spanner run a GC threshold sweep tied to the oldest active read timestamp. If a long-running transaction or an analytical query holds a snapshot open for hours, the GC horizon cannot advance, and dead versions accumulate unbounded. This is the operational failure mode every MVCC operator dreads: table bloat, index bloat, and eventually transaction ID wraparound (in systems using 32-bit XIDs) or storage exhaustion.
MVCC also redefines what anomalies are possible. It eliminates dirty reads and non-repeatable reads by construction, but write skew remains possible under Snapshot Isolation because two transactions can each read a consistent-but-stale snapshot, make disjoint writes that individually look valid, and commit concurrently — violating an invariant that spans both rows. Serializable Snapshot Isolation (SSI), used by Postgres and CockroachDB, closes this gap by tracking read-write dependency graphs at commit time and aborting one transaction if a dangerous rw-antidependency cycle is detected, rather than by locking.
In distributed deployments, MVCC’s snapshot semantics become the mechanism for achieving stale-read replicas and bounded staleness: a follower can safely serve reads at any timestamp for which it has received all committed versions, without coordinating with the leader, as long as it can prove no earlier-committed write is still in flight. This is precisely how Spanner’s TrueTime-bound snapshot reads and CockroachDB’s follower reads work — MVCC timestamps become the currency exchanged between the consistency protocol and the storage engine, turning what is fundamentally a local storage technique into a building block for multi-region read scalability.