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.

Key takeaways
  • 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.
The core insight

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 SS over a set of transactions T1,T2,…,TnT_1, T_2, \dots, T_n is a sequence of operations that 1. contains all operations of every TiT_i, in the same relative order that TiT_i 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 T1T_1 completely, then T2T_2 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 TjT_j reads a data item written by TiT_i, TiT_i's commit occurs before TjT_j'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. r1(A)r_1(A) means "T1T_1 reads AA", w2(B)w_2(B) means "T2T_2 writes BB", and cic_i/aia_i denote commit/abort of TiT_i.

Step 2 — Identify conflicting pairs. Two operations opi(X)op_i(X) from TiT_i and opj(X)op_j(X) from TjT_j (i≠ji \ne j) conflict if they touch the same item XX 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 Ti→TjT_i \to T_j whenever some operation of TiT_i precedes and conflicts with some operation of TjT_j. 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 O(m2)O(m^2) 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.

Figure
Interleaved schedule and its precedence graph for two transactions
Schedule S: interleaved operations of T1 (transfer) and T2 (audit read) time → r1(A) w1(A) r2(A) r2(B) w1(B) c1 T1: r1(A) w1(A) ................... w1(B) c1 T2: ............... r2(A) r2(B) c2 Conflicts: w1(A)-r2(A) is WR on A; r2(B) vs w1(B) is RW on B Precedence graph T1 T2 A (WR) B (RW) — cycle! Cycle T1→T2→T1 means this schedule is NOT conflict serializable.

Complexity & correctness

Building the precedence graph. With nn transactions and mm total operations in the schedule, a naive all-pairs conflict scan costs O(m2)O(m^2). A practical implementation instead groups operations by data item: for each of the dd distinct data items, list the operations touching it in schedule order (length mxm_x for item xx, with ∑xmx=m\sum_x m_x = m), and scan each such list once to emit conflict edges between adjacent-in-time differing transactions on that item, costing O(mx)O(m_x) per item and O(m)O(m) total. Building the graph is therefore O(m+n2)O(m + n^2) in the worst case (the n2n^2 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 nn nodes and E≤n2E \le n^2 edges, depth-first-search-based cycle detection and topological sorting both run in O(n+E)O(n + E), i.e., O(n2)O(n^2) worst case but typically far less since conflict graphs are sparse. So the entire serializability test for conflict serializability runs in roughly O(m+n2)O(m + n^2) time and O(m+n2)O(m + n^2) 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 TjT_j appearing before an operation of an "earlier" transaction TiT_i (earlier meaning TiT_i precedes TjT_j in the topological order). If these two adjacent operations conflicted, they would induce an edge Tj→TiT_j \to T_i in the precedence graph, contradicting that TiT_i precedes TjT_j 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 O(m2)O(m^2) 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 T1→T2→⋯→Tk→T1T_1 \to T_2 \to \cdots \to T_k \to T_1. Each edge Ti→Ti+1T_i \to T_{i+1} means some conflicting operation of TiT_i precedes one of Ti+1T_{i+1} in SS. Conflict-equivalence to a serial schedule S′S' requires that every conflicting pair of operations occurs in the same relative order in SS and S′S' (swapping non-conflicting operations never changes the order of conflicting ones, by definition of the swap). So S′S' must place T1T_1 before T2T_2 before …\dots before TkT_k before T1T_1 — an impossible cyclic ordering of what is supposed to be a linear (serial) sequence. Contradiction; hence no such S′S' exists, and SS 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 AA and BB, initially A=100A = 100, B=50B = 50.

  • T1T_1 (transfer 20 from A to B): `read(A); A := A - 20; write(A); read(B); B := B + 20; write(B); commit`
  • T2T_2 (audit, sum A + B): `read(A); read(B); commit`

Schedule S1S_1 (serial, T1T_1 then T2T_2):

StepOperationABNotes
1r1(A)r_1(A)10050T1T_1 reads A
2w1(A)w_1(A)8050T1T_1 writes A
3r1(B)r_1(B)8050T1T_1 reads B
4w1(B)w_1(B)8070T1T_1 writes B
5c1c_18070commit
6r2(A)r_2(A)8070T2T_2 reads A = 80
7r2(B)r_2(B)8070T2T_2 reads B = 70
8c2c_28070audit total = 150 ✓

Serial, trivially correct: audit sees 80+70=15080 + 70 = 150, consistent with the pre-transfer total 100+50=150100+50=150.

Schedule S2S_2 (interleaved, unsafe):

StepOperationABNotes
1r1(A)r_1(A)10050
2w1(A)w_1(A)8050A debited
3r2(A)r_2(A)8050T2T_2 reads A = 80 (post-debit)
4r2(B)r_2(B)8050T2T_2 reads B = 50 (pre-credit!)
5c2c_28050audit total = 130 ✗
6r1(B)r_1(B)8050
7w1(B)w_1(B)8070B credited
8c1c_18070

The audit total is 80+50=130≠15080 + 50 = 130 \ne 150: 2020 has "disappeared" mid-transfer from the auditor's point of view. Building the precedence graph for S2S_2: operation w1(A)w_1(A) (step 2) precedes and conflicts with r2(A)r_2(A) (step 3) on item A ⇒ edge T1→T2T_1 \to T_2. Operation r2(B)r_2(B) (step 4) precedes and conflicts with w1(B)w_1(B) (step 7) on item B ⇒ edge T2→T1T_2 \to T_1. The graph has both T1→T2T_1 \to T_2 and T2→T1T_2 \to T_1: a 2-cycle. By the theorem, S2S_2 is not conflict serializable — and indeed we just exhibited a wrong numeric outcome that matches neither (T1,T2)(T_1, T_2) nor (T2,T1)(T_2, T_1) order, confirming the theorem's prediction operationally, not just structurally.

Schedule S3S_3 (interleaved, but safe):

StepOperationABNotes
1r1(A)r_1(A)10050
2w1(A)w_1(A)8050
3r1(B)r_1(B)8050
4w1(B)w_1(B)8070T1T_1 fully done writing
5r2(A)r_2(A)8070T2T_2 starts only now
6c1c_18070
7r2(B)r_2(B)8070
8c2c_28070audit total = 150 ✓

Here the only conflict edges run T1→T2T_1 \to T_2 (both w1(A)w_1(A) before r2(A)r_2(A) and w1(B)w_1(B) before r2(B)r_2(B)), no edge the other way, so the graph is acyclic — conflict serializable, equivalent to serial order T1,T2T_1, T_2. 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

PitfallWhy it happensConsequence
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 conflictDefinition says "at least one write," easy to mis-skimPhantom conflict edges added, wrongly reporting non-serializability
Building conflict edges from any pair sharing an item, ignoring orderTreating conflict as symmetric/undirectedMissing the correct edge direction, corrupting the topological sort
Ignoring aborted transactions' operationsAssuming all operations in a schedule commitAn aborted transaction's writes must be excluded (or handled via recoverability rules), not treated as ordinary conflicts
Assuming conflict serializability and view serializability coincideBoth are called "serializability"; the distinction is not obvious at first lookRejecting 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 aloneIgnoring recoverabilityA 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
PA
Philip A. Bernstein
born 1948 · Distributed systems and database theorist who, with Nathan Goodman, formalized serializability theory for concurrent transaction processing in the late 1970s and early 1980s

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
History
1971
— Concurrent transaction processing systems (early airline reservation and banking systems) expose lost-update and dirty-read bugs in production, motivating formal study.
1976
— Kung and Papadimitriou, and independently Eswaran, Gray, Lorie, and Traiger at IBM, formalize serializability as the correctness criterion for concurrency control, connecting it to two-phase locking.
1979
— Bernstein and Goodman publish foundational surveys unifying conflict serializability, precedence graphs, and concurrency control protocols into a single theoretical framework.
1981
— Papadimitriou proves that testing view serializability is NP-complete, cementing conflict serializability as the practically tractable standard.
1993
— Berenson et al.'s critique of ANSI SQL isolation levels reignites interest in precisely relating relaxed isolation to the serializability benchmark, feeding directly into modern Isolation Levels design.

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 TjT_j reads data written by TiT_i, then TiT_i must commit before TjT_j commits. A schedule can be serializable yet non-recoverable (if TjT_j commits after reading TiT_i's uncommitted write and TiT_i later aborts, the commit of TjT_j 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.

Before this

Where this leads