Correctness: Ensuring Safe Concurrent Execution
Concept. A correct concurrent schedule produces the result of a valid serial order. That property is conflict-serializability, and the test for it is a conflict graph with no cycle.
Intuition. When Mickey's and Minnie's playlist updates interleave at the action level, the schedule is correct as long as the resulting Listens table looks identical to "all of Mickey, then all of Minnie" or the reverse. The per-step order can change; the end state must match some serial run. This page goes from "the database reorders for speed" to the exact test for when that reordering is safe.
The naive ways both failed: serial is too slow, free-for-all corrupts. The fix interleaves for speed while keeping the result correct, which demands a precise test for when an interleaving is safe. The rest of this page makes it mechanical: name the schedule types, define what a conflict is, and turn any schedule into a graph whose cycles flag the unsafe ones.
Key Definitions
An action is a single read or write of one row. A transaction is a sequence of them:
Figure: Actions: a transaction's reads and writesA transaction is a sequence of actions, each one a read or write of a single row, written like T1.R(A) for transaction T1 reading row A. T1 reads A, writes A, then reads B. Blue boxes are reads, orange labels are writes. Correctness tracks only the order of these actions, not the values they compute.
Figure 1. A transaction is a sequence of actions, each one a read or write of a single row, written T1.R(A) for transaction T1 reading row A. Blue labels are reads, orange labels are writes. Correctness tracks only their order, not the values they compute.
Figure: Serial, interleaved, and serializable schedulesThree labeled rows, each a schedule for T1 and T2 drawn as four action boxes left to right in time order, each box written like T1.R(A), blue for a read and orange for a write. The rows are Serial, Interleaved and Serializable. The Serializable row carries a second run of boxes after an equivalence sign, labeled a serial run. Color key: blue read, orange write.
| Serial | |||
|---|---|---|---|
| T1.R(A) | T1.W(A) | T2.R(B) | T2.W(B) |
| Interleaved | |||
|---|---|---|---|
| T1.R(A) | T2.R(B) | T1.W(A) | T2.W(B) |
| Serializable | |||
|---|---|---|---|
| T1.R(A) | T2.R(B) | T1.W(A) | T2.W(B) |
| T1.R(A) | T1.W(A) | T2.R(B) | T2.W(B) |
Figure 2. Serial runs one transaction's actions to completion, then the next. Interleaved mixes them in time. Serializable is an interleaving whose result matches some serial order. Blue boxes are reads, orange writes; the T1 or T2 prefix names the transaction, and the page tracks only the R/W order, the macro view.
Serializable is not serial. A serial schedule never overlaps; a serializable one interleaves freely, but its result still matches some serial order.
When do actions conflict?
A conflict is two actions on the same row where at least one is a write: their order changes the result, so the database must never swap that pair. Every other pair is free. The three failures from Naive Concurrency were exactly these conflicts, and they come in three kinds, set by which action reads and which writes.
Figure: When do actions conflict?A 2 by 2 matrix comparing one action of transaction T1 (rows) against one action of transaction T2 (columns), both on the same row. Only read-read commutes (green, no conflict); read-write, write-read, and write-write are conflicts (red). A conflict needs two transactions, the same row, and at least one write.
Figure 3. A conflict needs two transactions, the same row, and at least one write. Only read-read commutes; the other three each leave an order-dependent result, each a named anomaly: read-write a stale read, write-read a dirty read, write-write a lost update. Every conflict becomes a directed edge in the conflict graph, next.
Only conflicts are pinned; every other pair of actions can move. The next section turns that into a test for a whole schedule, then shows why that freedom is exactly enough.
How to Test for Conflict-Serializability
Build a conflict graph, then check for a cycle
The conflict graph has one node per transaction and a directed edge from Ti to Tj whenever an action of Ti conflicts with and precedes an action of Tj. The schedule is conflict-serializable exactly when that graph is acyclic.
Acyclic: a topological sort gives a serial order that respects every conflict edge, so the schedule is CS.
Cycle: two conflicts demand opposite orders, no serial order satisfies them all, so the schedule is not CS.
Figure: The conflict-serializability test: build a conflict graph, then check for a cycleThe test in three steps. First, one node per transaction (T1, T2, T3 as violet circles). Second, a directed edge for every conflict, here forming the acyclic chain T3 to T1 to T2. Third, look for a cycle: the two outcomes are shown as a green box, no cycle means conflict-serializable because a serial order respects every edge, and a red box, a cycle means not conflict-serializable because two edges demand opposite orders. Color key: violet is the graph, green is the safe outcome, red is the unsafe outcome.
Figure 4. The whole test in three steps. Build one node per transaction, add a directed edge for every conflict, then look for a cycle. No cycle (green) means a serial order respects every edge, so the schedule is conflict-serializable; a cycle (red) means two edges demand opposite orders, so none does.
Example 1: Concert Ticket Booking
A concrete scenario: 3 transactions and 2 shared variables.
Setup: The Scenario
Scenario
Concert venue with 2 sections. Three customers simultaneously try to book tickets during a high-demand sale.
Shared Variables:
• A: VIP section tickets (initially: 10)
• B: General section tickets (initially: 22)
Transaction Actions
Each customer performs specific booking actions on the shared ticket counters.
T1: R(A), W(A) - books a VIP seat
T2: R(A), R(B), W(B) - checks VIP, then books General
T3: R(B), W(B) - books a General seat
Expected Results
Once all three transactions commit, the counts must land here under any correct schedule.
Final Values:
• A: 9 tickets
• B: 20 tickets
Serial Schedule S1: T1 → T2 → T3
Figure: A serial schedule: T1, then T2, then T3, with no overlapA MacroSchedule shown as a table of Transaction, Action, Variable, Step, and Value, one row per operation in step order. Each transaction runs as one contiguous block, bracketed and labeled on the left, so the serial schedule reads as one block per transaction. Color key: blue is a read, orange is a write.
| Transaction | Action | Variable | Step | Value |
|---|---|---|---|---|
| T1 | R | A | 1 | 10 |
| T1 | W | A | 2 | 9 |
| T2 | R | A | 3 | 9 |
| T2 | R | B | 4 | 22 |
| T2 | W | B | 5 | 21 |
| T3 | R | B | 6 | 21 |
| T3 | W | B | 7 | 20 |
Figure 5. A serial schedule runs T1, then T2, then T3 with no overlap. The brackets on the left make the shape plain: each transaction's actions form one contiguous block, the visual signature of serial. It ends at A=9, B=20 with ACID intact, but with no parallelism it is the slowest of the three.
Conflict Identification
Figure: A serial schedule has no conflict cycleConflict-serializability analysis: a table of conflicting actions (Txn 1, Txn 2, Variable, and the step in each), then a conflict graph with one node per transaction and a directed edge per conflict, and a verdict. A cycle (drawn in red) means the schedule is not conflict-serializable; an acyclic graph is serializable and equivalent to the serial order shown.
| Txn 1 | Txn 2 | Variable | Step (Txn 1) | Step (Txn 2) |
|---|---|---|---|---|
| T1 | T2 | A | 2 | 3 |
| T2 | T3 | B | 4 | 7 |
| T2 | T3 | B | 5 | 6 |
| T2 | T3 | B | 5 | 7 |
| Transaction | Action | Variable | Step |
|---|---|---|---|
| T1 | R | A | 1 |
| T1 | W | A | 2 |
| T2 | R | A | 3 |
| T2 | R | B | 4 |
| T2 | W | B | 5 |
| T3 | R | B | 6 |
| T3 | W | B | 7 |
Figure 6. Even a serial schedule has conflicts wherever transactions touch a shared variable: a W-R on A (T1 → T2), a W-W on B (T2 → T3), four in all. They chain T1 → T2 → T3, which is exactly the serial order that produced them.
Example 2: Interleaved Schedule Analysis
Now interleave the transactions. Two interleaved schedules below show the difference between conflict-serializable and non-conflict-serializable.
Schedule S1': Our First Interleaved Schedule
This first interleaving mixes the actions but preserves correctness.
Figure: S1': the same three transactions, interleavedA MacroSchedule shown as a table of Transaction, Action, Variable, Step, and Value, one row per operation in step order. The transactions interleave, so no blocks are bracketed. Color key: blue is a read, orange is a write.
| Transaction | Action | Variable | Step | Value |
|---|---|---|---|---|
| T1 | R | A | 1 | 10 |
| T2 | R | A | 2 | 10 |
| T1 | W | A | 3 | 9 |
| T2 | R | B | 4 | 22 |
| T2 | W | B | 5 | 21 |
| T3 | R | B | 6 | 21 |
| T3 | W | B | 7 | 20 |
Figure 7. S1' interleaves T1, T2, and T3 instead of running them serially. Whether the interleaving stays correct depends entirely on its conflict graph, built next.
Figure: S1' has no cycle in its conflict graph, so the interleaving is safeConflict-serializability analysis: a table of conflicting actions (Txn 1, Txn 2, Variable, and the step in each), then a conflict graph with one node per transaction and a directed edge per conflict, and a verdict. A cycle (drawn in red) means the schedule is not conflict-serializable; an acyclic graph is serializable and equivalent to the serial order shown.
| Txn 1 | Txn 2 | Variable | Step (Txn 1) | Step (Txn 2) |
|---|---|---|---|---|
| T2 | T1 | A | 2 | 3 |
| T2 | T3 | B | 4 | 7 |
| T2 | T3 | B | 5 | 6 |
| T2 | T3 | B | 5 | 7 |
| Transaction | Action | Variable | Step |
|---|---|---|---|
| T2 | R | A | 1 |
| T2 | R | B | 2 |
| T2 | W | B | 3 |
| T1 | R | A | 4 |
| T1 | W | A | 5 |
| T3 | R | B | 6 |
| T3 | W | B | 7 |
Figure 8. The conflict graph of S1' has four conflicts and no cycle, so S1' is conflict-serializable: it matches the serial order T2 → T1 → T3 (or T2 → T3 → T1). The faster interleaving is still correct.
Schedule S2': Our Second Interleaved Schedule
This second interleaving creates conflicts that break serializability.
Figure: S2': a different interleaving, with an extra T2 write on BA MacroSchedule shown as a table of Transaction, Action, Variable, Step, and Value, one row per operation in step order. The transactions interleave, so no blocks are bracketed. Color key: blue is a read, orange is a write.
| Transaction | Action | Variable | Step | Value |
|---|---|---|---|---|
| T1 | R | A | 1 | 10 |
| T2 | R | A | 2 | 10 |
| T1 | W | A | 3 | 9 |
| T2 | R | B | 4 | 22 |
| T2 | W | B | 5 | 21 |
| T3 | R | B | 6 | 21 |
| T3 | W | B | 7 | 20 |
| T2 | W | B | 9 | 20 |
Figure 9. S2' is a different interleaving of the same three transactions. The extra T2 write on B breaks it; the conflict graph next shows how. Its final values happen to match the serial run, so the totals are not the tell: the cycle is.
Figure: S2' has a cycle in its conflict graph, so the interleaving is unsafeConflict-serializability analysis: a table of conflicting actions (Txn 1, Txn 2, Variable, and the step in each), then a conflict graph with one node per transaction and a directed edge per conflict, and a verdict. A cycle (drawn in red) means the schedule is not conflict-serializable; an acyclic graph is serializable and equivalent to the serial order shown.
| Txn 1 | Txn 2 | Variable | Step (Txn 1) | Step (Txn 2) |
|---|---|---|---|---|
| T2 | T1 | A | 2 | 3 |
| T2 | T3 | B | 4 | 7 |
| T2 | T3 | B | 5 | 6 |
| T2 | T3 | B | 5 | 7 |
| T3 | T2 | B | 6 | 9 |
| T3 | T2 | B | 7 | 9 |
Figure 10. The conflict graph of S2' contains a cycle, so S2' is not conflict-serializable, and the interleaving is unsafe.
Conflicts Identified
We compute the conflicting actions and the Conflict Graph:
The cycle lives on variable B. T2 writes B at step 5, then T3 reads B at step 6, so T2's value reaches T3 and the order must be T2 before T3 (edge T2 → T3). But T3 writes B at step 7, then T2 writes B again at step 9, overwriting T3, so the order must also be T3 before T2 (edge T3 → T2). Those two B-conflicts point opposite ways, giving the cycle T2 → T3 → T2.
Why the cycle test works
You have now watched the cycle test decide two schedules: S1' was acyclic and safe, S2' held a cycle and was not. But why does an acyclic graph mean safe? Because you can slide the schedule into a serial order. Two actions that do not conflict swap with no effect, so slide the non-conflicting neighbours until each transaction's actions sit together in one block. What is left is a serial schedule. The only adjacencies you cannot slide past are the conflicts, the graph's own edges, and they fix which block comes first. When the edges form a cycle, two of them demand opposite orders, the schedule will not untangle, and no serial order exists, which is exactly what trapped S2'.
Figure: Sliding an interleaved schedule into a serial one by swapping non-conflicting neighboursAn interleaved schedule of four actions across two transactions. T1's two tiles (read A, write B), highlighted in violet, start split apart with a T2 action between them. The goal is to swap non-conflicting neighbours to gather T1's actions together, and then T2's, into contiguous blocks, which is a serial schedule. Markers between tiles show which pairs may swap (violet) and the one write-write conflict on B that may not (red). One free swap of the middle pair gathers T1 into one block and T2 into the next, giving the serial order T1 then T2. Color key: violet marks T1 and the free swaps, red marks the conflict that cannot move.
| Can we swap this into a serial schedule? | |||
|---|---|---|---|
| T1 R(A) | T2 R(A) | T1 W(B) | T2 W(B) |
| T1 block | T2 block | ||
|---|---|---|---|
| T1 R(A) | T1 W(B) | T2 R(A) | T2 W(B) |
Figure 11. Can we swap an interleaved schedule into a serial one? The goal is to gather each transaction's actions into one contiguous block. T1's two tiles (violet) start apart, with a T2 action between them. A neighbour pair may swap only when it does not conflict; here the middle pair touches different rows, so one swap drops T1 into a block and T2 into the next, leaving the serial order T1 then T2. The write-write conflict on B (red) is the one pair that cannot move. A schedule untangles like this exactly when its conflict graph is acyclic.
Two things follow, and they are the whole reordering story:
-
Non-conflicting actions reorder freely. That overlap is where the throughput comes from.
-
Whole transactions have no fixed order. The database runs them in any sequence the conflicts allow, and never promises T1 before T2.
Need a guaranteed order? Put both in one transaction, so their actions form a single block nothing can slide into.
The Landscape of Schedule Classes
A schedule is serializable when its result matches some serial run, which is the correctness goal. But serializability is expensive to check directly: proving it means comparing the outcome against every serial order, for every possible data value. Conflict-serializable is the practical stand-in, a stricter class decided by one mechanical test, the acyclic conflict graph. The classes nest.
Figure: How schedule classes fit togetherNested hierarchy: every S2PL schedule is a 2PL schedule, every 2PL schedule is conflict-serializable, every conflict-serializable schedule is serializable
Figure 12. Each ring is a set of schedules, strictly contained in the next: every conflict-serializable schedule is serializable, every two-phase-locking (2PL) schedule is conflict-serializable, and Strict-2PL sits inside 2PL. The gap between serializable and CS is a few rare blind-write schedules (a write that never reads its row), serializable yet not CS. The containment is also a difficulty gradient: the outer ring is checkable only by brute force, CS by the mechanical cycle test, and the 2PL rings, which the Two-Phase Locking page builds a few pages on, need no test at all, since the protocol enforces membership.
Because CS is sufficient for serializability and mechanical to check, it is the working standard: when this course says "serializable," it means conflict-serializable. The cycle test tells you whether a finished schedule is safe; it does not produce safe ones. Two-Phase Locking does: it yields only schedules already inside the CS ring, so the database never runs the test at runtime. The rest of this section builds that machinery, starting with the lock itself, top to bottom.