Concurrency Control, Part I: How Databases Achieve Concurrency Through Conflict-Free Scheduling

In my previous blog, I explored how serializability ensures isolation across transactions while linearizability guarantees real-time consistency of single objects.

This post builds on that discussion by diving deeper into serializability, essentially exploring how valid serial transaction orders are nothing but a means to achieve concurrency through conflict-free scheduling.

We already understand that concurrency control is fundamentally about ensuring that transactions running in parallel behave as though they were executed one after another. This property is called Serializability, and it’s the gold standard for correctness. The keyword here, however, that demands sincere attention is "behave" because if transactions were executed serially, it would be highly inefficient. Upon closer inspection, we see that overlapping transaction operations can indeed be safely ordered, as long as they don’t conflict. This may seem like a narrow window for concurrency, allowing transactions to reorder themselves while running independently, except when they conflict but it apparently has a huge impact on performance.

Of course, when conflicts do occur, they must be detected and handled carefully, a topic I’ll explore in my next blog. But before that, it’s worth understanding how databases create room for concurrency by recognising and scheduling non-conflicting operations to run safely in parallel.

This principle of conflict-free scheduling forms the basis for conflict-serializable and view-serializable schedules. These are underpinned by different concurrency control mechanisms such as PCC, OCC, MVCC, dependency graph analysis and timestamp ordering, respectively, which I’ll discuss in an upcoming post.

Let’s see what these schedules look like in practice.

Conflict Serializability

A schedule is conflict-serializable if it can be transformed by swapping only non-conflicting operations into a schedule that is equivalent to some serial execution of the same transactions.

Two operations conflict if they:
1. belong to different transactions,
2. access the same data item,
3. at least one of them is a write.

So, R1(X)W2(X), W1(X)R2(X), and W1(X)W2(X) pairs are in conflict.

Non-conflicting, operations would be R1(X)R2(X) and ones that involve different data items. For instance, R1(X)W2(Y), W1(X)R2(Y) and W1(X)W2(Y).

Because reordering non-conflicting operations preserves the outcome, conflict serializability ensures the schedule is equivalent to some serial execution guaranteeing correctness.

Looking at the examples from my previous blog post,

Given the following transaction operations:

Transaction Operation
T1 X = X - 10
T1′ Y = Y + 10
T2 Y = Y - 5
T2′ Z = Z + 5

Here, both T1′ and T2 access the same data item Y. One reads it, the other writes it. This creates a read–write conflict that fixes their relative order in any serializable schedule. All other operations (X and Z) are independent and can be freely interleaved.

Valid serial orders would be:
1. T1 T1' T2 T2' (T1 -> T2)
2. T2 T2' T1 T1' (T2 -> T1)

And some of the conflict serializable schedules would be:

T1 T2 T1' T2' (Equivalent to serial order T2 -> T1), and
T2 T1 T2' T1' (Equivalent to serial order T2 -> T1)

Notice that in both of these schedules, T1′ (which updates Y) must come after T2 (which reads or modifies Y) because they are engaged in a read-write conflict on the same item. If they were swapped, the outcome would change, breaking serializability.

In contrast, operations like T1 (on X) and T2′ (on Z) can freely reorder, since they touch different items and are non-conflicting.

View Serializability

While conflict serializability enforces strict ordering on all RW, WR, and WW conflicts, view serializability is more relaxed. It is a superset of conflict-serializable schedules, because in certain cases a conflict can be safely ignored without affecting the correctness of the outcome.

The classic case is with write-write conflicts W1(X)W2(X). If transaction T1 writes X = 5 but then T2 overwrites it with X = 7 and the value from T1 is never observed by anyone. In this case, the system does not need to abort T1. It can simply ignore T1’s write. The final state is still X = 7, which is the same as if T1’s write never happened.

If W1(X) arrived before W2(X), then it would just be part of normal schedule, and we would keep it. But if W1(X) arrives late, after W2(X) has already committed, the database can treat it as obsolete and skip persisting it altogether.

However, there is one important caveat: if any operation did read the value written by W1(X), we can't safely ignore it anymore. This write now matters to the schedule because it influenced another transaction. Thomas' Write Rule doesn't apply to it anymore. Ignoring it would break view serializability. The system must either abort T1 or reject the conflicting read.

Conclusion

Conflict serializability is stricter than view serializability. Every conflict-serializable schedule is also view-serializable, but not vice versa.

View serializability allows the system to safely accept more schedules, by recognising that some operations are obsolete and can be discarded without affecting correctness.

Together, these forms of serializability reveal how databases achieve concurrency through conflict-free scheduling i.e., by reordering or skipping operations that don’t interfere, systems preserve correctness while unlocking parallelism.

In the next post, we’ll look at what happens when transactions do conflict and how databases handle those situations to preserve serializability.


References

  1. Database Internals
  2. Designing Data-Intensive Applications
Show Comments