0%
Database Internals: Build the Engine
Storage Structures
Durability
Query Processing
Access Paths
Distribution
Isolation Levels and Anomalies
Isolation is the hardest of the four transactional properties to state precisely, and almost every production bug in this area comes from somebody believing a guarantee the engine never offered.
The property in one sentence
A transaction should behave as though nothing else were running, even though many things are. Everything in this lesson is a partial, cheaper approximation of that sentence.
Starting from the ideal rather than from the levels is what makes the levels comprehensible. They are not points on a scale of goodness, they are named sets of things the engine declines to prevent.
What the ideal is called
Serializability. A set of concurrent transactions is serializable if the result is identical to running them one at a time in some order, with no requirement about which order.
The some order freedom is the part most often misread. Serializability does not promise the order you expected or the order they arrived in, only that the outcome matches some single-file order, which is why it alone does not prevent surprising results.
Why the ideal is not simply provided
Guaranteeing it requires either preventing conflicting work from running concurrently or detecting conflicts and aborting one party, and both cost throughput on workloads that would mostly have been fine.
Framing the cost honestly explains why weaker levels exist. Most concurrent transactions do not actually conflict, so a mechanism strict enough to catch the ones that do will constrain the many that would not have.
Why isolation is not the same as correctness
An application can be wrong under perfect serializability, because serializability says nothing about which order is chosen, and two orders can give different legitimate answers.
Separating the two prevents a common misattribution. A requirement that one specific transaction wins is a requirement about ordering, which the application must express, and no isolation level will supply it.
What reads and writes each need protecting from
A read needs protection from seeing a partial or doomed change. A write needs protection from being lost or from being made on the basis of a fact that stops being true. These are different problems with different costs.
Splitting them this way explains the shape of the levels. The cheap levels protect reads well and writes poorly, because protecting a read is a matter of what to show and protecting a write is a matter of what to prevent.
Why protecting reads is cheap
The engine can keep older versions of data and show a reader a consistent earlier state, which requires no coordination with writers at all. Readers never block and writers never wait for them.
Understanding why this is nearly free explains why almost every engine offers it. Keeping versions is a cost already paid for rollback, so showing a reader a past state reuses machinery that exists anyway.
Why protecting writes is expensive
A write's correctness often depends on something read earlier, and guaranteeing that the fact still holds means either holding it still or checking it at commit time and aborting. Both interfere with other work.
This asymmetry is the single most useful thing to carry from the section. It is why the gap between the cheap levels and the strict ones is almost entirely about writes, and why the anomalies that survive at the cheap levels are write anomalies.
What a transaction's read set and write set are
The data it read and the data it modified. Almost every mechanism in the next three lessons is about tracking one or both and comparing them against other transactions'.
Naming the two sets now pays off repeatedly. Locking holds the read set still, snapshot isolation fixes what the read set sees, and serializable snapshot isolation checks whether the read set changed, which is three answers to one question.
Why aborting is a legitimate mechanism rather than a failure
Refusing to let a transaction commit preserves the ideal just as well as making it wait, so an abort is one of the two tools available rather than evidence that something has gone wrong.
Accepting this early changes how the rest of the lesson reads. An engine that aborts a conflicting transaction has done its job correctly, and an application treating every abort as an error has declined half the mechanisms on offer. The strict levels are mostly implemented by aborting, so a willingness to retry is part of the price of the guarantee rather than an unfortunate quirk of a particular engine.
Why the question is about conflicts rather than about simultaneity
Two transactions touching different data can run in any interleaving with no consequence. The entire problem concerns overlapping sets, which is usually a small fraction of concurrent work.
This is what makes optimistic approaches viable. If conflicts are rare, assuming success and checking afterwards is cheaper than preventing failure in advance, and the whole of this lesson's practical advice follows from how rare conflicts actually are in a given workload.
Why the standard chose to define levels by what they permit
The committee could not require a mechanism, since engines differed, so it described levels by which anomalies each allows. That choice has caused thirty years of confusion.
Knowing the reason for the definition's shape makes its weaknesses predictable. A level defined by three forbidden anomalies says nothing about the hundreds of behaviours it neither forbids nor permits explicitly.
Why a level is a floor and not a description
An engine at a given level must prevent the anomalies that level forbids, and it may prevent more. So two engines at the same named level can behave very differently and both be conformant.
This is the fact that makes porting an application between engines dangerous. The level name is a promise about a minimum, and application code frequently depends on whatever the first engine happened to provide above it.
Why the application's own structure matters as much as the level
A transaction that reads, decides and writes in separate round trips is exposed to anything that happens between them, and no level protects against the gap outside the transaction.
Raising this early sets up the section on verification. A great deal of what looks like an isolation problem is a transaction boundary drawn in the wrong place, which is an application fix rather than a configuration one.
Why the subject rewards precision
The failures are rare, non-deterministic, and leave data that looks plausible, so they are discovered long after the fact by somebody reconciling numbers rather than by an error.
This is the reason the lesson spends its effort on naming anomalies exactly. A problem you can name is one you can look for in code review, and a vague sense that concurrency is hard is not actionable.
Why the level is rarely where an investigation ends
Most reported isolation problems turn out to be a transaction boundary in the wrong place, a decision made on data read before the transaction began, or a read served by a replica, and a level affects none of those.
Saying this before the anomalies arrive sets the right expectation. The vocabulary in this lesson is necessary for discussing the problem precisely, and the fix is more often a change to where the transaction starts and ends than a change to a setting. The level is the first thing to establish because it is cheap to establish, not because it is usually the answer.
What to take from isolation
Three things. Serializability means the outcome matches some single-file order, with no promise about which, so it is not the same as the application getting the order it wanted. Protecting reads is cheap because older versions already exist for rollback, while protecting writes is expensive because it means holding a fact still or aborting, which is why every anomaly surviving at the cheap levels is a write anomaly. And a level name is a floor rather than a description, so two conformant engines at the same level can behave very differently.
A named isolation level is a statement about the minimum an engine must prevent, not a description of what it does. Application code written against one engine routinely depends on behaviour that engine provided above its stated level, which is why a migration between engines at the same named level can introduce anomalies without any application change. The level name is where the investigation starts, never where it ends.