Loading learning content...
With the concepts of Read Timestamp (RTS) and Write Timestamp (WTS) established, we can now formalize the Read Rules of the Basic Timestamp Protocol. These rules define precisely when a read operation is allowed, when it must be rejected, and what updates occur on success.
The read rules embody a simple but powerful principle: a transaction should only read values that existed at the time the transaction logically began. Since transaction timestamps define their position in the serialization order, a transaction with timestamp TS(T) should see the database state as it was at logical time TS(T)—meaning it should only read values written by transactions with smaller timestamps.
By the end of this page, you will understand the formal read rules of the Basic Timestamp Protocol, be able to determine whether any read operation should succeed or fail, and know exactly how timestamps are updated upon successful reads. You'll see these rules in action through detailed examples and edge cases.
Before formalizing the rules, let's understand the underlying principle. In timestamp ordering, we want the execution to be equivalent to running all transactions serially in timestamp order. For reads, this means:
A transaction T with timestamp TS(T) should read the value of X that was most recently written by a transaction with timestamp < TS(T).
But here's the challenge: we're not actually running transactions serially. They're interleaved. So when T tries to read X, the current value might have been written by a transaction T' where TS(T') > TS(T). In this case, T would be reading a "future" value—one that shouldn't exist yet from T's perspective.
If T reads a value written by T' where TS(T') > TS(T), we have a temporal violation. In any equivalent serial schedule, T would execute before T', so T should see the value that existed before T' wrote it—not T's value. This is why we must check WTS(X) before allowing reads.
The Core Insight:
The Write Timestamp WTS(X) tells us the timestamp of the transaction that wrote the current value of X. If TS(T) < WTS(X), then a younger transaction wrote the current value, and T cannot read it without violating serializability.
Conversely, if TS(T) ≥ WTS(X), the current value was written by an older or equal-aged transaction, which is consistent with T's position in the serialization order.
| Condition | Meaning | Decision |
|---|---|---|
| TS(T) < WTS(X) | Current value written by younger transaction | REJECT read, abort T |
| TS(T) ≥ WTS(X) | Current value written by older/equal transaction | ALLOW read |
We now present the complete, formal read rules for the Basic Timestamp Protocol. When transaction T requests to read data item X:
Algorithm: Read(X) by Transaction T
1. IF TS(T) < WTS(X) THEN
// T is trying to read a value written in T's "future"
Abort T and restart with new, larger timestamp
RETURN FAILURE
END IF
2. // Read is allowed
Let v = current value of X
3. IF TS(T) > RTS(X) THEN
RTS(X) := TS(T) // Update read timestamp
END IF
4. RETURN v (success)
The RTS update in step 3 isn't required for the current read to succeed—it's for future writes. By recording that T has read X, we ensure that any future transaction T' where TS(T') < TS(T) cannot overwrite X and invalidate T's read. RTS is the mechanism that protects readers from belated writers.
Atomicity Requirement:
Steps 1-3 should be atomic (or at least serialized for each data item). Consider what could go wrong without atomicity:
T has now read a "future" value despite the initial check passing. Proper implementation requires either:
Let's trace the read rules through several scenarios to build intuition.
Example 1: Simple Successful Read
| State/Step | Values | Action/Result |
|---|---|---|
| Initial | X = 100, WTS(X) = 15, RTS(X) = 15 | — |
| T(TS=25) requests Read(X) | Check: 25 ≥ 15? | Yes, proceed |
| Retrieve value | v = 100 | — |
| Update RTS | 25 > 15? | Yes, RTS(X) := 25 |
| Final | X = 100, WTS(X) = 15, RTS(X) = 25 | Return 100 |
T successfully reads X because its timestamp (25) is greater than the writer's timestamp (15). The RTS is updated to 25, protecting the value from writes by transactions with timestamps between 15 and 25.
Example 2: Read Rejection
| State/Step | Values | Action/Result |
|---|---|---|
| Initial | X = 200, WTS(X) = 40, RTS(X) = 40 | — |
| T(TS=20) requests Read(X) | Check: 20 ≥ 40? | No, ABORT |
| Abort T | — | T rolled back, restarts as T' |
| T'(TS=50) requests Read(X) | Check: 50 ≥ 40? | Yes, proceed |
| Complete read | RTS(X) := 50 | Return 200 |
T cannot read X because the current value was written by a transaction with timestamp 40, which is greater than T's timestamp 20. After restarting with a larger timestamp (50), the read succeeds.
Example 3: Multiple Concurrent Reads
| Step | Transaction | WTS(X) | RTS(X) Before | RTS(X) After | Result |
|---|---|---|---|---|---|
| 1 | T50 reads X | 10 | 10 | 50 | Success |
| 2 | T30 reads X | 10 | 50 | 50 | Success (no RTS update) |
| 3 | T60 reads X | 10 | 50 | 60 | Success (RTS updated) |
| 4 | T5 reads X | 10 | 60 | 60 | ABORT (5 < WTS) |
Notice that steps 1-3 all succeed despite being out of timestamp order. As long as each reader's timestamp is ≥ WTS, the read is allowed. The RTS only updates when a younger transaction reads. This is one advantage of timestamp ordering: multiple readers never block each other.
Real systems must handle various edge cases. Let's examine some important scenarios.
Edge Case 1: Reading After Your Own Write
What happens when a transaction reads data it has previously written?
| Step | Operation | State After | Notes |
|---|---|---|---|
| 1 | T(TS=30) writes X = 50 | WTS(X) = 30, RTS(X) = 30 | T writes X |
| 2 | T(TS=30) reads X | Check: 30 ≥ 30? Yes | Same transaction reads |
| 3 | Read completes | RTS(X) = 30 (no change) | 30 > 30 is false, no update |
A transaction can always read its own writes because TS(T) = WTS(X) after T writes X. This is consistent with the intuition that transactions should see their own changes.
Edge Case 2: Reading Newly Inserted Data
When a transaction inserts a new row:
Only transactions with TS ≥ TS(T) can read the new row. This makes sense: the row didn't exist before T, so transactions "older" than T shouldn't see it.
Edge Case 2 touches on the phantom problem. If T1 scans a range and then T2 inserts a new row in that range (where TS(T2) > TS(T1)), T1 won't see it. But if T1 re-scans the range, it still won't see T2's row (blocked by WTS check). This prevents some phantoms, but not all scenarios—range locks or predicates are still needed for complete phantom prevention.
Edge Case 3: Cascading Read Failures
If a transaction reads multiple data items, each read is checked independently. If any read fails, the entire transaction aborts:
In basic timestamp ordering, the "work" done before the failed read (reads don't modify data, but might have influenced later writes) must be carefully handled during abort.
Edge Case 4: Equal Timestamps
What if TS(T) = WTS(X) exactly?
The condition is TS(T) ≥ WTS(X), so TS(T) = WTS(X) is allowed. This typically happens when:
For the second scenario, implementation must ensure unique timestamps through:
Read-only transactions present an interesting optimization opportunity. Since they never write, they can never cause write-after-read conflicts. This observation leads to potential optimizations.
The Problem with Naive Read-Only Handling:
In basic timestamp ordering, every read updates RTS(X) (if the reader is younger than all previous readers). For read-only analytical queries scanning millions of rows, this creates massive write amplification—turning read-only workloads into write-heavy workloads.
Correctness of Skipping RTS Updates:
Why is it safe for read-only transactions to skip RTS updates? Consider:
Caveat: This optimization requires reliable identification of read-only transactions. If a transaction initially appears read-only but later writes, the optimization fails. Systems may require explicit READ ONLY declarations or use speculative execution with fallback.
Read-only optimization is one reason MVCC has largely replaced pure timestamp ordering. In MVCC, reads never update any timestamp—they simply read the appropriate version based on their snapshot. This completely eliminates write amplification for reads, making MVCC far more practical for mixed workloads.
Implementing the read rules correctly requires careful attention to several practical concerns.
| Challenge | Description | Solution Approach |
|---|---|---|
| Atomicity | Check + read + update must be atomic | Short-lived latches or atomic operations |
| Memory Overhead | Every data item needs WTS + RTS | 8-16 bytes per tuple |
| Cache Effects | RTS updates cause cache line invalidation | Batching, lazy updates, or epoch-based approaches |
| Index Entries | Index entries may need separate timestamps | Depends on indexing strategy |
| Recovery | Timestamps must be logged and recoverable | Include in WAL records |
Interaction with Buffer Pool:
When a transaction reads a data item:
Logging Considerations:
For durability and recovery:
Pure timestamp ordering with full RTS tracking is rarely implemented in modern production databases due to these overheads. Instead, systems typically use MVCC (which avoids most RTS overhead) or hybrid approaches that combine timestamp-based reasoning with selective locking.
The read rules we've formalized are one half of the Basic Timestamp Protocol. Let's understand how they fit into the complete picture.
The Asymmetry:
Notice that reads only check WTS (one condition) while writes check both RTS and WTS (two conditions). This asymmetry reflects the different conflict scenarios:
This is why writes are more likely to be rejected than reads—there are more ways for a write to conflict.
The No-Wait Property:
A key feature of the read rules is that they never wait. The decision is immediate:
There's no queue of waiting transactions as in lock-based protocols. This eliminates deadlocks entirely (transactions never wait for each other) but introduces the cost of aborts and restarts.
We've now formalized the complete read rules of the Basic Timestamp Protocol. Let's consolidate the key concepts:
What's Next:
With the read rules complete, the next page formalizes the Write Rules—the more complex algorithm that handles write operations. Write rules must check both RTS and WTS, making them the stricter half of the protocol. We'll also see how the Thomas Write Rule can relax one of these checks to reduce unnecessary aborts.
You now understand the formal read rules of the Basic Timestamp Protocol. You can determine whether any read should succeed or fail, and you understand the RTS update mechanism that protects successful reads from later writes. Next, we'll formalize the complementary write rules.