Serializability and Schedules
Imagine a bank database processing two transactions at once. One transfers money from account A to account B. Another reads the total balance of A and B for an audit report. If the database interleaves their reads and writes carelessly, the audit might see money that has vanished mid-transfer. It could be debited from A but not yet credited to B. The total looks wrong even though neither transaction, run alone, has any bug. This is the central problem that concurrency control in database systems must solve. It cannot be solved by inspecting transactions in isolation. It requires reasoning about the schedule — the actual interleaving of their operations in time. Real database systems run hundreds or thousands of transactions concurrently. This keeps throughput high and hardware utilized. Making transactions run one at a time (serially) would be correct but unacceptably slow. The challenge is to allow interleaving for performance. At the same time, the outcome must be indistinguishable from some serial execution. This guarantee is what the "I" (Isolation) in ACID Properties actually promises. Serializability is the formal correctness criterion that captures this idea precisely. A concurrent schedule is correct if it is equivalent to some serial schedule of the same transactions. This article builds the vocabulary and mental model needed to reason about concurrent execution rigorously. It covers what a schedule is, and what makes two schedules equivalent. It explains why serial schedules are the gold standard. It also shows how we classify schedules by how close they come to that standard. This machinery is the foundation on which every concurrency control protocol is built and judged. That includes locking, timestamp ordering, optimistic validation, and snapshot isolation. The payoff is conceptual clarity that pays for itself immediately in later material. Once you can define serializability formally, checking whether a specific interleaving is safe becomes a mechanical graph problem. This is developed under Conflict Serializability. Once you understand that full serializability is expensive to guarantee, you can appreciate why real systems relax it deliberately. That relaxation is the subject of Isolation Levels. Everything downstream in transaction processing rests on the definitions given here.
- A schedule is a time-ordered sequence of the read/write/commit/abort operations from a set of concurrently executing transactions.
- A serial schedule runs transactions one after another with no interleaving; it is trivially correct because each transaction sees a consistent, unmodified-by-others state.
- Serializability requires that a concurrent schedule produce the same effect as some serial schedule of the same transactions — this is the correctness benchmark for isolation.
- Two main flavors exist: conflict serializability (equivalence via swapping non-conflicting operations) and view serializability (equivalence via matching reads/final-writes); conflict serializability is stricter but efficiently checkable.
- Schedules are also classified by recoverability — whether commits can be undone safely — which is orthogonal to, but equally essential alongside, serializability.
- Real systems rarely enforce full serializability by brute force; instead protocols (locking, timestamps, MVCC) are designed to guarantee it or a controlled relaxation of it.
A concurrent schedule is correct exactly when it is equivalent, in its net effect, to some serial (one-transaction-at-a-time) execution of the same transactions.
The idea
A transaction is a sequence of read and write operations on data items, bracketed by begin and commit/abort, as established when studying Transaction States and Atomic Execution. When multiple transactions run concurrently, the database system interleaves their operations to improve throughput and resource utilization. The resulting interleaved sequence of operations — reads, writes, commits, and aborts from all participating transactions, in the order the system actually executes them — is called a schedule. Formally, a schedule over a set of transactions is a sequence of operations that 1. contains all operations of every , in the same relative order that itself specifies, and 2. interleaves operations from different transactions in some order chosen by the scheduler (the concurrency control component). A schedule is serial if the operations of each transaction occur consecutively, with no interleaving from other transactions — that is, the schedule is equivalent to running completely, then completely, and so on. A serial schedule is automatically correct in the sense that matters to a database: since transactions are assumed individually correct (each one, run alone starting from a consistent state, leaves the database in a consistent state — the "C" of ACID), running them one at a time preserves consistency by simple induction. The catch is that serial execution forfeits all benefits of concurrency.
Serializability is the criterion that reconciles these two goals: a schedule is serializable if it is equivalent to some serial schedule over the same set of transactions. "Equivalent" needs a precise meaning, and the field distinguishes two standard notions:
- Conflict equivalence. Two operations from different transactions conflict if they access the same data item and at least one of them is a write. Two schedules are conflict-equivalent if one can be transformed into the other by repeatedly swapping adjacent, non-conflicting operations of different transactions. A schedule is conflict serializable if it is conflict-equivalent to some serial schedule.
- View equivalence. Two schedules are view-equivalent if (a) for every data item, each transaction reads the same value in both schedules (the same "who wrote what I read" relationships hold), and (b) the final writer of each data item is the same transaction in both schedules. A schedule is view serializable if it is view-equivalent to some serial schedule.
Every conflict-serializable schedule is view-serializable, but not vice versa: view serializability is a strictly weaker (more permissive) condition, at the cost of being computationally harder to check (view serializability testing is NP-complete in general, whereas conflict serializability is checkable in polynomial time — details of the exact algorithm belong to Conflict Serializability). Because of this tractability gap, virtually all production concurrency control protocols target conflict serializability, and it is the working definition assumed whenever a system says it "guarantees serializable execution." A separate but equally important axis classifies schedules by recoverability: can the system correctly recover from an abort? A schedule is recoverable if, whenever transaction reads a data item written by , 's commit occurs before 's commit. Non-recoverable schedules can force a "domino effect" of cascading aborts or, worse, make correct recovery impossible after a crash. Recoverability and serializability are independent properties — a schedule can be serializable yet non-recoverable, and real systems must guarantee both.
How it works
Reasoning about serializability proceeds in three conceptual steps, each of which becomes an algorithm in practice: (1) represent the schedule, (2) identify conflicts, (3) check for an equivalent serial order.
Step 1 — Represent the schedule. A schedule is written as a sequence of operations tagged by transaction, e.g. means " reads ", means " writes ", and / denote commit/abort of .
Step 2 — Identify conflicting pairs. Two operations from and from () conflict if they touch the same item and at least one is a write. There are three conflict types: write-read (WR, a "dirty read" hazard if uncommitted), read-write (RW, an "unrepeatable read" hazard), and write-write (WW, a "lost update" hazard). Order between conflicting operations is fixed by the schedule and cannot be swapped without changing behavior; order between non-conflicting operations can be swapped freely without changing the final database state or what any transaction reads.
Step 3 — Test for serializability. For conflict serializability, the standard mechanism is the precedence graph (also called a serialization graph), built as follows: create one node per transaction, and draw an edge whenever some operation of precedes and conflicts with some operation of . The fundamental theorem is: > A schedule is conflict serializable if and only if its precedence graph is acyclic. If acyclic, any topological sort of the graph gives an equivalent serial order. This test, and its full justification, is developed in depth under Conflict Serializability; here we give the mechanism because it is the operational meaning of "how do you check whether a schedule works." ```pseudocode // Determine whether schedule S is conflict serializable. // S is given as an ordered list of operations, each tagged with its transaction id. function IS_CONFLICT_SERIALIZABLE(S): transactions := distinct transaction ids appearing in S graph := new DirectedGraph(nodes = transactions) for each pair of operations (op_a, op_b) in S with op_a occurring before op_b: if op_a.transaction != op_b.transaction and op_a.item == op_b.item and (op_a.type == WRITE or op_b.type == WRITE): graph.addEdge(op_a.transaction -> op_b.transaction) // conflict edge if graph.hasCycle(): return (false, null) else: order := graph.topologicalSort() return (true, order) // 'order' is an equivalent serial schedule ``` This runs by scanning all pairs of operations sharing an item; in a real implementation you would group operations by data item first to avoid the naive pair scan (see complexity section). Cycle detection and topological sort on the resulting graph are standard linear-time graph algorithms. A companion, purely conceptual, pseudocode captures what it means for a schedule to be serial in the first place — the anchor definition everything else refers back to: ```pseudocode // A serial schedule for transactions T_1 ... T_n in some order pi is simply // running each one to completion, uninterrupted, before starting the next. function EXECUTE_SERIAL(T_list, order): for i in order: T := T_list[i] for each operation op in T.operations: perform(op) // no other transaction's operation is interleaved commit(T) ``` Serializability theory then says: schedule S is correct if there exists some `order` such that `EXECUTE_SERIAL` on that order produces the same read-values and final database state as S.
Complexity & correctness
Building the precedence graph. With transactions and total operations in the schedule, a naive all-pairs conflict scan costs . A practical implementation instead groups operations by data item: for each of the distinct data items, list the operations touching it in schedule order (length for item , with ), and scan each such list once to emit conflict edges between adjacent-in-time differing transactions on that item, costing per item and total. Building the graph is therefore in the worst case (the bounds the number of distinct edges between transaction pairs), which in practice is close to linear in the size of the schedule.
Cycle detection and serial order. Once the graph has nodes and edges, depth-first-search-based cycle detection and topological sorting both run in , i.e., worst case but typically far less since conflict graphs are sparse. So the entire serializability test for conflict serializability runs in roughly time and space (to store the schedule and the graph) — polynomial, hence tractable for real deployment as an offline auditing tool, even though no production system literally builds this graph online for every schedule (protocols instead guarantee acyclicity by construction, e.g., two-phase locking, as covered under Conflict Serializability).
Why acyclicity characterizes conflict serializability (proof sketch). If the precedence graph is acyclic, the schedule is conflict serializable. Take any topological order of the transactions consistent with the graph's edges — one exists because the graph is a DAG. Claim: the schedule can be transformed into the serial schedule following this order purely by swapping adjacent non-conflicting operations, hence it is conflict-equivalent to that serial schedule. Proof by a swapping/exchange argument: consider any operation pair out of topological order, i.e., an operation of a "later" transaction appearing before an operation of an "earlier" transaction (earlier meaning precedes in the topological order). If these two adjacent operations conflicted, they would induce an edge in the precedence graph, contradicting that precedes in a topological order of an acyclic graph. So any such adjacent out-of-order pair must be non-conflicting, and can be swapped without changing the schedule's effect. Repeating this bubble-sort-style swapping process — a standard argument bounded by swaps — terminates with the fully serial order, and since every swap preserves conflict-equivalence (by definition), the original schedule is conflict-equivalent to that serial schedule. If the schedule is conflict serializable, its precedence graph is acyclic. Suppose for contradiction the graph has a cycle . Each edge means some conflicting operation of precedes one of in . Conflict-equivalence to a serial schedule requires that every conflicting pair of operations occurs in the same relative order in and (swapping non-conflicting operations never changes the order of conflicting ones, by definition of the swap). So must place before before before before — an impossible cyclic ordering of what is supposed to be a linear (serial) sequence. Contradiction; hence no such exists, and is not conflict serializable. Together these two directions establish the acyclicity theorem exactly, and give an efficient, mechanically checkable characterization of correctness — a rare case in concurrency theory where "correct" reduces cleanly to a textbook graph property.
Honest tradeoffs. Conflict serializability is sufficient but not necessary for "true" correctness in the broader sense of view serializability — some view-serializable schedules (typically ones involving "blind writes," writes not preceded by a read of the same item) are not conflict serializable, yet still produce a valid serial-equivalent outcome. Testing view serializability directly is NP-complete, so systems accept the stricter, cheaper-to-verify conflict serializability as their practical target, sacrificing a small amount of theoretical permissiveness for tractable enforcement. Likewise, no commercial system runs this graph algorithm live per-transaction; instead it is used as an analytical tool to prove that a given concurrency control protocol (locking, timestamps, multiversion) always yields acyclic precedence graphs by construction.
Worked example
Consider two transactions on a bank database with accounts and , initially , .
- (transfer 20 from A to B): `read(A); A := A - 20; write(A); read(B); B := B + 20; write(B); commit`
- (audit, sum A + B): `read(A); read(B); commit`
Schedule (serial, then ):
| Step | Operation | A | B | Notes |
|---|---|---|---|---|
| 1 | 100 | 50 | reads A | |
| 2 | 80 | 50 | writes A | |
| 3 | 80 | 50 | reads B | |
| 4 | 80 | 70 | writes B | |
| 5 | 80 | 70 | commit | |
| 6 | 80 | 70 | reads A = 80 | |
| 7 | 80 | 70 | reads B = 70 | |
| 8 | 80 | 70 | audit total = 150 ✓ |
Serial, trivially correct: audit sees , consistent with the pre-transfer total .
Schedule (interleaved, unsafe):
| Step | Operation | A | B | Notes |
|---|---|---|---|---|
| 1 | 100 | 50 | ||
| 2 | 80 | 50 | A debited | |
| 3 | 80 | 50 | reads A = 80 (post-debit) | |
| 4 | 80 | 50 | reads B = 50 (pre-credit!) | |
| 5 | 80 | 50 | audit total = 130 ✗ | |
| 6 | 80 | 50 | ||
| 7 | 80 | 70 | B credited | |
| 8 | 80 | 70 |
The audit total is : has "disappeared" mid-transfer from the auditor's point of view. Building the precedence graph for : operation (step 2) precedes and conflicts with (step 3) on item A ⇒ edge . Operation (step 4) precedes and conflicts with (step 7) on item B ⇒ edge . The graph has both and : a 2-cycle. By the theorem, is not conflict serializable — and indeed we just exhibited a wrong numeric outcome that matches neither nor order, confirming the theorem's prediction operationally, not just structurally.
Schedule (interleaved, but safe):
| Step | Operation | A | B | Notes |
|---|---|---|---|---|
| 1 | 100 | 50 | ||
| 2 | 80 | 50 | ||
| 3 | 80 | 50 | ||
| 4 | 80 | 70 | fully done writing | |
| 5 | 80 | 70 | starts only now | |
| 6 | 80 | 70 | ||
| 7 | 80 | 70 | ||
| 8 | 80 | 70 | audit total = 150 ✓ |
Here the only conflict edges run (both before and before ), no edge the other way, so the graph is acyclic — conflict serializable, equivalent to serial order . The audit correctly reports 150 even though the two transactions' operations were interleaved in real time. This is the whole point: interleaving is fine, as long as it does not create a conflict cycle.
Common pitfalls & traps
| Pitfall | Why it happens | Consequence |
|---|---|---|
| Confusing "serial" with "serializable" | The words look similar; students assume serializable means "runs one at a time" | Misses the entire point — serializable schedules are usually interleaved, not serial |
| Forgetting that read-read pairs never conflict | Definition says "at least one write," easy to mis-skim | Phantom conflict edges added, wrongly reporting non-serializability |
| Building conflict edges from any pair sharing an item, ignoring order | Treating conflict as symmetric/undirected | Missing the correct edge direction, corrupting the topological sort |
| Ignoring aborted transactions' operations | Assuming all operations in a schedule commit | An aborted transaction's writes must be excluded (or handled via recoverability rules), not treated as ordinary conflicts |
| Assuming conflict serializability and view serializability coincide | Both are called "serializability"; the distinction is not obvious at first look | Rejecting a legitimately correct (view-serializable) schedule, or over-trusting an algorithm that only checks the weaker property |
| Treating serializability as sufficient for real-world correctness alone | Ignoring recoverability | A serializable schedule can still be unrecoverable if a transaction commits after reading uncommitted data from one that later aborts |
Pioneers & history
Origins · 1 minPhilip A. Bernstein and how this was discovered
Serializability theory did not emerge from a single breakthrough moment but from the practical crisis of early multi-user database and operating systems in the 1970s, where concurrent access to shared data was causing silent corruption bugs that no one had a rigorous vocabulary to describe or prevent.
Origins · 1 minhow this was discovered
Where this matters
Every production relational database — PostgreSQL, MySQL/InnoDB, Oracle, SQL Server, SQLite — implements a concurrency control subsystem whose entire design goal is to guarantee some form of serializability (or an explicitly weaker, documented isolation level) without ever literally computing a precedence graph online. Two-phase locking protocols enforce serializability by construction, guaranteeing acyclic precedence graphs as a side effect of lock ordering rules. Optimistic concurrency control and multiversion concurrency control (MVCC), used in PostgreSQL's snapshot isolation and in systems like Google Spanner and CockroachDB, validate transactions against exactly this kind of conflict analysis at commit time, often using a more refined graph that also tracks read-write anti-dependencies (relevant to detecting so-called write skew anomalies). Distributed databases and cloud transaction services (Spanner, CockroachDB, FoundationDB) advertise "serializable" as their strongest isolation guarantee precisely in the sense defined here, and their published architecture papers explicitly cite precedence-graph-style arguments to justify correctness. Compilers and static analyzers for database application code, and formal verification tools for smart contracts and financial ledgers, use serializability checking as an offline auditing technique — exactly the graph algorithm given above — to certify that a set of observed transaction logs could have arisen from some safe serial order. Understanding schedules and serializability is also the prerequisite for reading and evaluating any concurrency control protocol paper, since every such protocol's correctness proof ultimately reduces to "this protocol never produces a cyclic precedence graph."
Ask the tutor: Can you walk me through why two-phase locking always produces an acyclic precedence graph?
Is every serial schedule automatically serializable?
Yes, trivially: a schedule is serializable exactly when it is equivalent to some serial schedule, and a serial schedule is equivalent to itself. Serial schedules form the reference class against which serializability is measured, so they sit at the "bottom" of the definition rather than being an interesting case to test. What is interesting, and what real systems care about, is proving that an interleaved schedule — one that actually captures the performance benefits of concurrency — is still equivalent to one of these baseline serial orders. That equivalence is exactly what the precedence-graph acyclicity test verifies.
Why not just run every transaction serially and avoid the whole problem?
Serial execution is correct but throws away concurrency entirely: every transaction must wait for all previous ones to fully finish, even when they touch completely unrelated data. On multi-core hardware and in systems serving thousands of simultaneous users, that would collapse throughput and make interactive applications unusable — imagine every bank customer queued behind a single teller processing transactions one at a time nationwide. Serializability theory exists precisely to let the system interleave operations for performance while mathematically guaranteeing the same outcome as some serial order, capturing correctness without paying the full performance cost of literal serial execution.
What is the difference between conflict serializability and recoverability?
They answer different questions. Conflict (or view) serializability asks whether the net effect of an interleaved schedule matches some serial order — a question about correctness of the final state and observed reads. Recoverability asks whether the system can safely undo transactions after a crash or abort, specifically requiring that if reads data written by , then must commit before commits. A schedule can be serializable yet non-recoverable (if commits after reading 's uncommitted write and later aborts, the commit of cannot be safely undone). Production systems must guarantee both properties simultaneously, usually via the same locking or versioning protocol.
Why is conflict serializability preferred over view serializability in practice, if view serializability accepts more schedules?
Practicality beats permissiveness here. Testing whether a schedule is view serializable is NP-complete in general — no known efficient algorithm exists, and none is expected given standard complexity assumptions. Testing conflict serializability, by contrast, reduces to building a precedence graph and checking for cycles, an operation with polynomial (near-linear) time complexity. Since concurrency control protocols must make admit/reject decisions in real time as transactions execute, a criterion that is expensive to verify is operationally useless, even if it is theoretically more permissive. Systems therefore design protocols that provably guarantee conflict serializability by construction, accepting the small loss of permissiveness for tractability.
Does a cycle in the precedence graph always mean data corruption happened?
A cycle means the schedule is not conflict serializable — there is no serial order the schedule is equivalent to, which means the outcome is not guaranteed to match any single-transaction-at-a-time execution. It does not automatically mean the specific numeric values are "wrong" in every case (some cyclic schedules might coincidentally produce a value consistent with some serial order for that particular data), but there is no guarantee of correctness, and in general concurrency control protocols must reject or restructure such schedules because they cannot certify the outcome is safe. The worked bank example shows a concrete case where the cycle does correspond to visibly wrong output, which is the typical and expected symptom.
How does this connect to isolation levels like READ COMMITTED or SNAPSHOT?
Full serializability, as defined here, is the strongest isolation guarantee a database can offer — it is the top of the isolation hierarchy. Weaker isolation levels (READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SNAPSHOT ISOLATION) deliberately permit certain kinds of anomalies — dirty reads, non-repeatable reads, phantoms, or write skew — that a fully serializable schedule would forbid, trading some correctness guarantees for higher concurrency and throughput. The formal definitions of these anomalies are stated directly in terms of the conflict types (WR, RW, WW) introduced here, so the machinery of schedules and conflicts is the shared language used to precisely compare isolation levels, a topic developed fully under Isolation Levels.
Can a schedule with only read operations ever be non-serializable?
No. If every operation in a schedule is a read, no two operations ever conflict (conflict requires at least one write), so the precedence graph has no edges at all — trivially acyclic — and any interleaving of pure reads is conflict-equivalent to any serial order, including all of them being equivalent to each other. This matches intuition: read-only transactions can never interfere with each other's outcomes since none of them modifies shared state. Non-serializability only becomes possible once at least one transaction performs a write that some other transaction's read or write depends on.