Timestamp-Ordering Protocol

Imagine a bank database processing thousands of concurrent transfers, deposits, and balance checks every second. Two-phase locking can enforce Conflict Serializability. But locks make transactions wait. Waiting transactions can deadlock, stall, or pile up behind slow ones. What if, instead of making transactions negotiate over locks, we simply assigned each transaction a birth certificate? This certificate is a timestamp. We then force all data conflicts to respect the order of those certificates. That is the idea behind the Timestamp-Ordering (T/O) Protocol, a concurrency control scheme. It guarantees serializability without ever acquiring a lock. Every transaction is stamped with a unique timestamp when it starts. Every data item remembers the timestamps of the most recent transactions that read and wrote it. Whenever an operation would violate the required "younger transactions act later" ordering, the offending transaction is aborted. It is then restarted with a fresh, later timestamp. Conflicts are resolved instantly and optimistically, at the moment they occur. This happens rather than by blocking. This protocol matters because it represents a fundamentally different philosophy from lock-based schedulers. It is non-blocking for reads and writes; no transaction ever waits for a lock. It is deadlock-free by construction. It produces schedules that are provably equivalent to the specific serial order defined by the timestamps. These properties make it attractive in distributed and real-time systems. In such systems, blocking is expensive. A global notion of "logical time" is easy to generate there, but coordinating locks across sites is not. This article builds the protocol from first principles. It covers how timestamps are assigned. It explains how read/write timestamps on data items enforce ordering. It details the exact abort/rewrite rules. It shows why the resulting schedules are always serializable. Finally, it explores how the ideas evolve into the Thomas Write Rule. From there, they progress into <a class="wiki-link" href="/wiki/cs-db-multiversion-concurrency-control" data-track="wiki_article.body.wiki_link">Multiversion Concurrency Control</a>, where old versions are kept instead of aborting transactions outright.

Key takeaways
  • Every transaction gets a unique, monotonically increasing timestamp `TS(T)` at start; it fixes T's position in the serial order the schedule must simulate.
  • Each data item Q stores `W-timestamp(Q)` and `R-timestamp(Q)` — the timestamps of the youngest transaction to write or read it.
  • A read or write that would require an "older" transaction to act after a "younger" one already has is rejected: the transaction is aborted and restarted with a new, later timestamp.
  • The basic T/O protocol guarantees conflict-serializable schedules equivalent to the timestamp order, with no locks and no deadlocks — but it is not recoverable by itself.
  • The Thomas Write Rule relaxes the write rule to safely ignore ("skip") obsolete writes instead of aborting, improving concurrency without breaking correctness.
  • T/O trades blocking for restarts; heavy contention causes cascading aborts, motivating extensions like buffering commits and, ultimately, <a class="wiki-link" href="/wiki/cs-db-multiversion-concurrency-control" data-track="wiki_article.body.wiki_link">Multiversion Concurrency Control</a>.
The core insight

Timestamp ordering enforces serializability by making every conflicting read/write obey the transactions' assigned time order — violations cause an abort-and-restart instead of a wait.

The idea

A schedule of concurrent transactions is conflict-serializable if it is equivalent to some serial execution of those same transactions, as captured by Conflict Serializability and its precedence-graph test. Two-phase locking achieves this indirectly, by preventing cycles in the conflict graph through blocking. The timestamp-ordering protocol takes a more direct route: it decides in advance which serial order the schedule must be equivalent to — the order of the transactions' timestamps — and then enforces every single conflicting operation to occur in a way consistent with that predetermined order. Formally, when a transaction `T` enters the system, the scheduler assigns it a unique timestamp `TS(T)`, drawn from a monotonically increasing source (a logical or system clock, or a counter). If `TS(Ti) < TS(Tj)`, the protocol guarantees the produced schedule behaves as if `Ti` executed entirely before `Tj` with respect to any data item they both access. To track this, every data item `Q` carries two auxiliary fields:

  • W-timestamp(Q): the largest timestamp of any transaction that successfully executed `write(Q)`.
  • R-timestamp(Q): the largest timestamp of any transaction that successfully executed `read(Q)`.

These two fields are the entire state the protocol needs — no lock tables, no wait queues. Whenever a transaction attempts to read or write `Q`, the scheduler compares `TS(T)` against the stored timestamps to check whether this operation is consistent with `T` acting at its assigned logical time. If a younger transaction has already read or written `Q` before an older transaction `T` gets around to it, then honoring `T`'s operation would violate the serial order — so `T` is aborted and restarted with a brand-new, larger timestamp, effectively pushing it later in the serial order it must respect.

How it works

The protocol's rules are split cleanly into a read rule and a write rule, each checking whether the requesting transaction is "too late" relative to some other transaction that has already touched the item.

Read rule. Transaction `T` issues `read(Q)`.

  • If `TS(T) < W-timestamp(Q)`: some transaction that is younger than T in serial order has already written a newer value of `Q`. Letting `T` read now would mean `T` reads a value from "the future" relative to its assigned position — impossible in any equivalent serial schedule. Reject: abort `T`, restart with a new timestamp.
  • Otherwise: the read is consistent with serial order. Execute `read(Q)`, and set `R-timestamp(Q) = max(R-timestamp(Q), TS(T))`.

Write rule. Transaction `T` issues `write(Q)`.

  • If `TS(T) < R-timestamp(Q)`: a younger transaction has already read `Q`, presumably reading an earlier value that existed before `T`'s write. Allowing `T`'s write now would retroactively change what that younger reader should have seen. Reject: abort `T`, restart with a new timestamp.
  • Else if `TS(T) < W-timestamp(Q)`: a younger transaction has already written a newer value of `Q`. Writing now would overwrite that newer value with an obsolete one, violating serial order. Under the basic protocol, reject: abort and restart. (The Thomas Write Rule, discussed below, instead simply ignores this write.)
  • Otherwise: the write is consistent with serial order. Execute `write(Q)`, and set `W-timestamp(Q) = TS(T)`.

Every abort restarts the transaction with a completely fresh timestamp — never reusing the old one — since the old timestamp is precisely what caused it to be "too late." ```pseudocode // Global structures for each data item Q: W_timestamp[Q] = 0 // or timestamp of item's initial write R_timestamp[Q] = 0 function START_TRANSACTION(): T.timestamp = next_unique_timestamp() // strictly increasing return T function READ(T, Q): if T.timestamp < W_timestamp[Q]: ABORT_AND_RESTART(T) // too late: value already overwritten else: value = disk_read(Q) R_timestamp[Q] = max(R_timestamp[Q], T.timestamp) return value function WRITE(T, Q, new_value): if T.timestamp < R_timestamp[Q]: ABORT_AND_RESTART(T) // too late: a younger reader already saw an earlier value else if T.timestamp < W_timestamp[Q]: // Basic protocol: ABORT_AND_RESTART(T) // too late: a younger writer already wrote a newer value // Thomas Write Rule (alternative): // IGNORE the write (do not perform it, do not update W_timestamp[Q]) // and let T continue as if the write succeeded. else: disk_write(Q, new_value) W_timestamp[Q] = T.timestamp function ABORT_AND_RESTART(T): rollback_effects(T) T.timestamp = next_unique_timestamp() // new, strictly larger timestamp resubmit(T) ``` The following diagram shows the state machine each `read`/`write` request goes through, and how a single data item's `R-timestamp`/`W-timestamp` pair mediates every decision.

Figure
Decision flow for a read or write request under Timestamp Ordering
Data item Q R-ts(Q), W-ts(Q) (updated on success) T issues op(Q) op? read(Q): check TS(T) < W-ts(Q)? read yes: ABORT restart w/ new TS yes (too old) no: perform read R-ts(Q)=max(R-ts,TS(T)) no write(Q): check TS(T)<R-ts(Q)? write yes: ABORT restart w/ new TS yes no: check TS(T) < W-ts(Q)? no basic: ABORT Thomas rule: IGNORE write yes no: perform write W-ts(Q) = TS(T) no

Complexity & correctness

Time complexity. Each `read` or `write` operation performs a constant number of comparisons against `R-timestamp(Q)` and `W-timestamp(Q)`, plus (on success) a constant-time update of one field. So the per-operation overhead is `O(1)`, matching the cost of a lock-table lookup in two-phase locking but without any wait. If a transaction is aborted and restarted, its entire work is redone; the amortized cost depends on the abort rate, which grows with contention — in the worst case (adversarial timestamp assignment or extremely hot data items), a transaction could be repeatedly aborted, giving no worst-case upper bound on total restarts without an added rule such as "always assign a strictly increasing timestamp so eventual success is guaranteed once the transaction becomes the oldest active one touching `Q`." In practice, contention is moderate and the expected number of restarts per transaction is small.

Space complexity. The protocol needs exactly two extra timestamp fields per data item — `O(1)` per item, `O(n)` total for `n` items — dramatically less than a full lock table, and no wait-for graph is needed at all (compare to deadlock detection under 2PL).

Correctness argument. The claim is: any schedule produced by the timestamp-ordering protocol is conflict-equivalent to the serial schedule in which transactions appear in timestamp order. *Proof sketch. Consider any two transactions `Ti` and `Tj` with `TS(Ti) < TS(Tj)` that both access a common data item `Q`, with at least one operation being a write (a conflicting pair, per the definition used in <a class="wiki-link" href="/wiki/cs-db-conflict-serializability" data-track="wiki_article.body.wiki_link">Conflict Serializability</a>). There are three conflict cases: 1. <strong>`Ti` reads `Q`, `Tj` writes `Q`.</strong> The write rule guarantees that when `Tj` writes, `TS(Tj) \ge R\text{-timestamp}(Q)` at that moment — otherwise `Tj` would be aborted. Since `Ti`'s successful read updated `R-timestamp(Q)` to at least `TS(Ti)` before that check could fail against a smaller `TS(Tj)`, and `Tj`'s write only succeeds when it is not smaller than the current `R-timestamp(Q)`, the successful ordering places `Ti`'s read chronologically before `Tj`'s write in execution — matching `Ti \to Tj` in timestamp order. 2. <strong>`Ti` writes `Q`, `Tj` reads `Q`.</strong> The read rule ensures `Tj`'s read is rejected whenever `TS(Tj) < W\text{-timestamp}(Q)`. If `Ti`'s write already succeeded and set `W-timestamp(Q) = TS(Ti)`, then for `Tj`'s subsequent read to succeed we need `TS(Tj) \ge TS(Ti)`, i.e., `Ti \to Tj`, consistent with timestamp order — and the read actually observes `Ti`'s write (or a later one), never a stale value from a transaction younger than `Ti`. 3. <strong>`Ti` writes `Q`, `Tj` writes `Q`.</strong> The basic write rule rejects `Tj`'s write whenever `TS(Tj) < W\text{-timestamp}(Q)`. A successful sequence of writes on `Q`, therefore, has `W-timestamp(Q)` monotonically increasing, so the final stored value reflects the write from the transaction with the largest timestamp among those that actually wrote — exactly the value a serial execution in timestamp order would leave behind. In every conflicting pair, the protocol's accept/reject rules force the effective order of execution on `Q` to agree with the timestamp order* of `Ti` and `Tj`. Since this holds for every conflicting pair across every data item, the precedence graph built over all transactions (the conflict graph used to test Conflict Serializability) can only have edges pointing from smaller to larger timestamps — an inherently acyclic ordering (a linear order can never induce a cycle). By the serializability theorem, an acyclic conflict graph implies the schedule is conflict-serializable, and specifically equivalent to the serial schedule ordered by timestamp. `∎`

Deadlock freedom. Because no transaction ever waits — every conflict is resolved immediately by aborting one side — there is no possibility of a circular wait, hence no deadlocks. This is a structural guarantee, not a probabilistic one.

Honest tradeoffs.

  • Starvation is possible: a transaction repeatedly restarted with new timestamps could, in principle, always end up "too late" again if it is unlucky and long-running while short transactions race past it. Practical implementations must sometimes freeze a transaction's timestamp temporarily or give priority boosts to prevent live-lock.
  • Cascading aborts: if `Ti` writes a value that `Tj` (with `TS(Tj) > TS(Ti)`) reads, and `Ti` later aborts for unrelated reasons, `Tj` must also abort — the schedule is not guaranteed recoverable without extra buffering (e.g., delaying commit of `Tj` until `Ti` commits).
  • More restarts under contention than a well-tuned lock-based scheme, because T/O aborts eagerly the instant an out-of-order access is detected, rather than allowing a transaction to wait for the resource to free up.
  • The Thomas Write Rule improves on the basic write rule: if `TS(Ti) < W-timestamp(Q)` but `TS(Ti) \ge R\text{-timestamp}(Q)`, then `Ti`'s write, if performed, would immediately be overwritten (in serial order) by the already-completed younger write — so it is safe to simply skip it rather than abort `Ti`. This preserves correctness (the final database state is identical to what a valid serial schedule would produce) while avoiding an unnecessary abort, strictly increasing concurrency over the basic protocol.

Worked example

Consider three transactions accessing a single data item `A`, with timestamps assigned at start:

TransactionTimestamp
T110
T220
T330

Initial state: `R-timestamp(A) = 0`, `W-timestamp(A) = 0`. Trace of interleaved operations, arriving in this real-time order (not timestamp order — that is the whole point):

StepOperationCheckResultR-ts(A)W-ts(A)
1T2 write(A)`TS(T2)=20 \ge R\text{-ts}=0` and `\ge W\text{-ts}=0`Success020
2T1 read(A)`TS(T1)=10 < W\text{-ts}=20`Abort T1 — restart with new `TS(T1')=40`020
3T3 write(A)`TS(T3)=30 \ge R\text{-ts}=0` but `TS(T3)=30 < W\text{-ts}=20`? — false, `30 \ge 20`Success030
4T1' read(A)`TS(T1')=40 \ge W\text{-ts}=30`Success, reads T3's value4030
5T2 read(A)(T2 attempts a later read) `TS(T2)=20 < R\text{-ts}=40`? Read rule only checks against `W\text{-ts}=30`: `20 < 30`Abort T2 — restart with new `TS(T2')=50`4030

Walk through the reasoning at each abort:

  • Step 2: T1 (timestamp 10, the "oldest") tries to read `A` after T2 (timestamp 20) has already written it. In the serial order the protocol must simulate, T1 comes before T2 — so T1 should have read `A`'s value before T2 overwrote it. But physically, T2's write already happened. There is no way to retroactively give T1 an "earlier" view, so T1 must be aborted and pushed to a later logical position (a new timestamp, 40, making it now "younger" than both T2 and T3).
  • Step 3: T3 (timestamp 30) writes after T2 (timestamp 20) wrote — this is fine since `30 > 20`, consistent with T2 acting before T3.
  • Step 5: T2 (original timestamp 20) tries to read `A`, but `W-timestamp(A)` is now 30 (set by T3, whose logical position is after T2). Reading now would mean T2 reads a value that (in serial-order terms) belongs to a later transaction — a violation. T2 must abort and restart at 50.

At the end, the surviving execution order (by final, possibly-restarted timestamps) is: T2 (write, 20) → T3 (write, 30) → T1′ (read, 40) → T2′ (pending). This matches a legitimate serial order — the protocol never let an inconsistency slip through; it paid for that guarantee with two restarts.

Common pitfalls & traps

PitfallWhy it happensFix
Reusing a transaction's old timestamp after abortSeems "simpler," but the old timestamp is exactly why it failedAlways assign a new, strictly larger timestamp on restart
Forgetting the write rule's two separate checksStudents often only check against `W-timestamp` and skip the `R-timestamp` checkWrite must check `R-timestamp(Q)` and `W-timestamp(Q)` — a younger reader also invalidates an older write
Assuming T/O schedules are automatically recoverableSerializability and recoverability are separate propertiesAdd commit-dependency buffering, or delay visible writes until commit, to avoid cascading aborts
Treating Thomas Write Rule as "always skip stale writes"The rule only applies when the write is safe to skip, i.e. `TS(T) \ge R\text{-timestamp}(Q)`Check the read-timestamp condition first; if `TS(T) < R\text{-timestamp}(Q)`, it is still a hard abort
Believing T/O has no overhead compared to lockingNo lock waits, but restarts redo full transaction logicCompare expected total work, not just per-operation cost, especially under high contention

Pioneers & history

The timestamp-ordering protocol emerged from the broader effort in the 1970s and 1980s to formalize concurrency control theoretically, connecting operating-systems-style scheduling with the emerging theory of database transactions and serializability.

Origins · 1 minPhilip A. Bernstein and how this was discovered
PA
Philip A. Bernstein
1949–present · Distributed databases and concurrency control theory

Philip Bernstein, alongside Nathan Goodman, formalized much of the theoretical foundation of timestamp-based and multiversion concurrency control in the late 1970s and early 1980s, publishing widely cited surveys that unified locking, timestamp-ordering, and optimistic methods under a common correctness framework based on serializability graphs.

History
1976
— Reed's PhD thesis at MIT introduces multiversion timestamp ordering as an alternative to locking for distributed data.
1978
— Bernstein and Goodman formalize timestamp-ordering protocols and prove their serializability within a general concurrency-control taxonomy.
1981
— Bernstein, Hadzilacos, and Goodman's synthesis work connects timestamp-ordering, two-phase locking, and validation-based (optimistic) protocols as three faces of the same serializability guarantee.
1980
s — Distributed database systems (e.g., SDD-1) adopt timestamp-ordering variants to avoid cross-site deadlock detection, motivated by the cost of coordinating locks over a network.
1990
s–present — Timestamp-ordering ideas are absorbed into snapshot isolation and multiversion designs used in modern engines, trading aborts for version retention.

Where this matters

Timestamp-ordering's core insight — resolve conflicts by comparing logical time instead of acquiring locks — persists throughout modern systems, even where the exact basic protocol described above is rarely deployed unmodified:

  • Distributed databases (e.g., early systems like SDD-1, and conceptually in Google's Spanner-style designs) use timestamps, sometimes tied to physical or hybrid logical clocks, to order transactions across machines without a centralized lock manager, since coordinating locks across a network is far more expensive than comparing timestamps.
  • Optimistic and multiversion concurrency control in systems like PostgreSQL, Oracle, and MySQL's InnoDB (under snapshot isolation) generalize the timestamp-ordering idea: instead of aborting a transaction whenever it is "too late," these systems keep multiple versions of each data item so that older transactions can still read a version consistent with their logical time — a direct evolution toward <a class="wiki-link" href="/wiki/cs-db-multiversion-concurrency-control" data-track="wiki_article.body.wiki_link">Multiversion Concurrency Control</a>.
  • Real-time and embedded databases, where predictable, non-blocking behavior matters more than raw throughput, favor timestamp-based schemes precisely because they are deadlock-free and never make a transaction wait indefinitely for a lock.
  • Version vectors and logical clocks in distributed systems (e.g., Lamport clocks, vector clocks) borrow the same conceptual machinery — comparing timestamps to establish a consistent partial or total order of events — that underlies this protocol's read/write rules.

Ask the tutor: How does the Thomas Write Rule change the correctness proof compared to the basic protocol?

How is a transaction's timestamp actually generated?

Any source producing strictly increasing, unique values works: a system-wide logical counter incremented atomically on each transaction start, the system clock (with tie-breaking, e.g., appending a site or process ID for uniqueness across distributed nodes), or a Lamport-style logical clock in distributed settings. The critical requirement is monotonicity and uniqueness — two transactions must never receive the same timestamp, and timestamps must be comparable so that "younger" and "older" are well-defined. In distributed databases, generating globally consistent timestamps efficiently (without a single bottleneck node) is itself a significant systems challenge, addressed by techniques like hybrid logical clocks.

Does timestamp ordering avoid all forms of blocking?

Yes, for the standard read/write operations described here — a transaction is never made to wait on another; it either proceeds immediately or is aborted immediately. This is its main appeal over two-phase locking, which can force transactions into wait queues and thus risks deadlock. However, "no blocking" does not mean "no cost": aborted transactions must redo their work, which under high contention can consume more total resources than a scheme that occasionally blocks briefly instead of restarting from scratch.

How does timestamp ordering compare to two-phase locking in practice?

Two-phase locking tends to perform better under moderate contention because waiting is often cheaper than a full restart, and it integrates naturally with lock-based recovery mechanisms. Timestamp ordering shines when blocking itself is expensive or hard to coordinate — for instance, across geographically distributed nodes — since comparing two numbers is far cheaper than negotiating a distributed lock. In practice, most production systems use neither pure protocol, favoring multiversion schemes that borrow ideas from both while minimizing aborts.

What is the Thomas Write Rule exactly, and why is it safe?

It modifies the basic write rule: if `TS(T) < W-timestamp(Q)` but `TS(T) \ge R-timestamp(Q)`, the write is not performed at all (rather than aborting T), and T proceeds as though the write succeeded. This is safe because, in serial order, T's write would immediately be shadowed by a later transaction's already-applied write to the same item — no transaction ever observes T's value anyway, so skipping it produces the identical final database state a valid serial schedule would produce, without wastefully aborting T.

Can starvation occur under timestamp ordering?

In principle, yes. A transaction could be aborted, restarted with a fresh (larger) timestamp, and then aborted again if it repeatedly loses races against other transactions writing the same hot data item. Because each restart pushes the transaction's logical position further into the "future," a poorly designed system with extremely high contention on a small set of items could see a transaction restarted many times. Practical systems mitigate this with priority aging, exclusive retry windows, or switching to a different concurrency control scheme for hot items.

How does timestamp ordering relate to conflict serializability formally?

Every schedule produced under timestamp ordering is conflict-equivalent to the specific serial schedule ordered by transaction timestamps, which is a stronger and more constructive claim than the general test used for Conflict Serializability. Rather than building a precedence graph after the fact and checking for cycles, timestamp ordering forces the graph to be acyclic by construction, because every conflicting operation pair is resolved to agree with the fixed linear order of timestamps, which can never contain a cycle.

Why is timestamp ordering considered a stepping stone to multiversion concurrency control?

Basic timestamp ordering discards old values once overwritten, which forces an abort whenever an older transaction needs a value that has already been overwritten by a younger one. Multiversion Concurrency Control observes that if the database instead kept the old version around, that aborted read could simply be served from the appropriate historical version, eliminating many unnecessary aborts while preserving the same timestamp-based serializability argument.

Before this

Where this leads