0%
Database Internals: Build the Engine
Storage Structures
Durability
Query Processing
Access Paths
Distribution
MVCC, Snapshots and Tuple Visibility
Multi-version concurrency control is the mechanism behind almost every default isolation level in use today, and the reason it won is not subtle once the costs are laid out.
The one idea
A write does not overwrite the old value, it creates a new version alongside it. A reader is given a version that was current at some chosen moment, so it never has to wait for a writer.
Starting from the one idea is worth doing because everything else is consequence. Every rule about visibility, every reclamation problem and every operational surprise follows from the decision not to destroy the previous value at the moment of the write.
Why keeping old versions is nearly free
The engine already has to keep the previous value in order to roll a transaction back, so the old version exists whether or not readers are allowed to see it.
This is the crucial economic observation and it is usually skipped. Versioning is not a new cost imposed to help readers, it is a decision to expose a cost already being paid for durability, which is why the mechanism is so widely affordable.
What the mechanism buys
Readers never block writers and writers never block readers. The only remaining conflicts are between two writers wanting the same item, which is a far smaller set than all conflicting pairs.
Stating the benefit precisely matters because it is often overstated. Write conflicts are untouched by versioning and still need the mechanism from the previous lesson, so what has been removed is exactly the read-write conflicts.
Why that removal is so valuable
Most workloads are overwhelmingly reads, so eliminating read-write conflict eliminates almost all waiting while leaving the genuinely contended writes to be handled by locking.
Putting the benefit in workload terms is the honest justification. On a workload that is mostly writes to the same rows, versioning buys very little, which is why its advantage is a statement about typical workloads rather than about concurrency in general.
What stamps a version
The identifier of the transaction that created it, and later the identifier of the transaction that superseded it. Those two numbers plus the commit status of each are enough to decide visibility.
Naming the stamps now makes the next section mechanical. Visibility is arithmetic on transaction identifiers rather than anything more sophisticated, which is why the test is cheap enough to run on every tuple examined.
Why transaction identifiers must be monotonic
They establish the order in which transactions began, which is what lets a reader decide whether a version was created before or after its own reference point.
Pointing at the ordering requirement explains a later problem. The identifier is a finite counter that must keep increasing, which creates the wraparound issue covered in the reclamation section and has no analogue in a locking engine.
Why a transaction identifier is not a timestamp
It orders transactions by when they started rather than by wall-clock time, and no clock is consulted, which means the ordering is exact and local to the engine.
Drawing the distinction prevents a common misconception. Engines that do use timestamps in distributed settings need clock synchronisation and all its difficulties, and a single-node engine deliberately avoids the whole problem by counting.
What a version is not
It is not a copy of the row kept for history, it is not visible to the application, and it is not retained for any purpose other than serving readers and rollbacks.
Ruling these out matters because the terminology invites confusion. Versioning is not a feature for querying the past, although some engines build one on top of it, and the versions vanish as soon as nobody can see them.
Why the mechanism is invisible when it works
Nobody waits, nothing aborts, and the only evidence of versioning is storage used by versions nobody needs yet, which is why operational problems with it are always about reclamation.
Setting this expectation early frames the whole lesson. The interesting material is not in the happy path, which is uneventful, but in what happens when versions cannot be removed.
Why the word snapshot is the right one
A reader is not given the newest data, it is given a consistent picture of the database as of a chosen moment, which is exactly what a snapshot means.
Insisting on the word is worthwhile because the implication is important. A long-running report sees a self-consistent database that is progressively more out of date, and that staleness is a feature rather than a defect.
Why consistency and recency are traded here
A reader cannot have both a stable picture and the newest values, because the newest values keep changing while it reads.
Naming the trade explicitly prevents a class of confused bug reports. A query returning data that is a few seconds old inside a long transaction is behaving exactly as specified, and the remedy is a shorter transaction rather than a configuration change.
Why an aborted transaction costs almost nothing
Its versions were written and nobody can see them, so rolling back means marking the transaction aborted rather than reversing any data.
Noting the cheapness of abort is a genuine advantage of the design. The versions left behind are reclaimed by the same process that reclaims superseded ones, so a failed transaction imposes no special work and no latency on anybody waiting.
What the mechanism does not fix
Any anomaly caused by two transactions both writing, since versioning only removes conflicts where one party reads. Lost update and write skew survive it entirely.
Repeating the boundary from the isolation lesson is justified here because this is where people assume otherwise. The mechanism's strength on reads is often mistaken for general protection, and it provides none for writes.
Why the mechanism needs no application cooperation
Nothing about the transaction API changes, no new error can arrive at the weaker level, and no code has to know that versions exist.
Spelling this out explains how the mechanism became ubiquitous quietly. An engine can adopt it and present exactly the same interface, which is why a great many applications depend on its behaviour without anybody having chosen it deliberately.
Why it is the default nearly everywhere
It gives read committed or snapshot isolation at close to no cost on read-heavy workloads, it requires no application changes, and its failure mode is storage growth rather than errors.
Explaining the adoption is useful for calibration. The mechanism did not win because it is more correct, it won because its cost profile matches how applications actually behave.
Why it changes what to worry about
Lock waits and deadlocks become rarer and are replaced by version storage, reclamation lag and the consequences of long-running transactions.
Framing it as a change of concerns rather than a removal of them is the accurate picture. An engine using versioning has a different operational surface, and the long transaction becomes the central villain in a way it is not under pure locking.
What to take from versioning
Three things. The one idea is that a write creates a new version rather than destroying the old one, and it is affordable because the old version already had to exist for rollback, so versioning exposes a cost already being paid. What it buys is the removal of read-write conflict specifically, which is most of the waiting on a read-heavy workload and none of it on a write-contended one. And it changes what to worry about rather than removing worry, replacing lock waits and deadlocks with version storage and reclamation lag, which is why long-running transactions matter more here than anywhere else.
Versioning is affordable because the engine already keeps the previous value in order to support rollback. The old version is not a new cost introduced to help readers, it is an existing cost being exposed to them. This is why the mechanism appears almost free on read-heavy workloads and why its real expense shows up somewhere else entirely, in deciding when a version may finally be discarded.