1Which anomaly can occur under snapshot isolation but not under serializability?
Serializability is the strongest isolation level for database transactions, guaranteeing that the outcome of executing transactions concurrently is equivalent to some serial (one-at-a-time) execution. It prevents all concurrency anomalies -- dirty reads, non-repeatable reads, phantom reads, and write skew.
Serializability is the gold standard for transaction isolation in databases. A schedule of concurrent transactions is serializable if its effect is equivalent to some serial schedule -- an ordering where transactions execute one at a time, with no interleaving. This guarantee eliminates all concurrency anomalies, including dirty reads, non-repeatable reads, phantom reads, and write skew. If your application logic is correct when transactions run serially, serializability ensures it remains correct under concurrent execution.
There are three main implementation strategies. Two-phase locking (2PL) is the classic pessimistic approach: transactions acquire read locks (shared) or write locks (exclusive) before accessing data, and release all locks only after committing or aborting. The 'two phases' are the growing phase (acquiring locks) and the shrinking phase (releasing locks). 2PL guarantees serializability but can cause deadlocks (two transactions each waiting for the other's lock) and reduces concurrency because locks block other transactions.
Serial execution literally runs transactions one at a time on a single thread. This sounds absurdly slow, but for workloads where transactions are short and all data fits in memory (like Redis, VoltDB, and H-Store), single-threaded execution is surprisingly fast -- it avoids all lock overhead, context switching, and cache invalidation. The constraint is that transactions must be short (no interactive multi-round transactions) and must not perform network I/O during execution.
Serializable snapshot isolation (SSI) is the modern optimistic approach used by PostgreSQL (since 9.1) and CockroachDB. Transactions read from a consistent snapshot (like regular snapshot isolation) and proceed without blocking. At commit time, the database checks for serialization conflicts (rw-antidependencies between concurrent transactions). If a conflict is detected, one transaction is aborted and retried. SSI provides serializability with much better read performance than 2PL because reads never block, but it can have higher abort rates under heavy contention.
The On-Call Doctor Problem (Write Skew)
A hospital requires at least one doctor on call at all times. Two doctors, Alice and Bob, are both on call. Alice checks: 'Two doctors on call? I can go off-call.' Bob simultaneously checks: 'Two doctors on call? I can go off-call.' Both proceed to remove themselves. Result: zero doctors on call -- a constraint violation. Under snapshot isolation, both reads see the 'before' state (two doctors), so both writes succeed. Under serializability, the database detects the conflict and aborts one transaction, ensuring at least one doctor remains on call.
PostgreSQL (SSI)
PostgreSQL implements serializable isolation via Serializable Snapshot Isolation (SSI) since version 9.1. Transactions read from MVCC snapshots, and the system tracks read-write dependencies between concurrent transactions. If it detects a dangerous structure (a cycle of rw-antidependencies), it aborts one transaction with a serialization failure. Applications must retry aborted transactions. SSI provides much better read concurrency than 2PL because reads never block writes.
CockroachDB
CockroachDB provides serializable isolation as its default (and only) isolation level. It uses an optimistic concurrency control mechanism where transactions read at a timestamp, write intents (provisional writes), and resolve conflicts at commit time. CockroachDB's approach is similar to SSI but operates across a distributed cluster with Raft-replicated ranges. Write-write conflicts cause transaction restarts; read-write conflicts push the reading transaction's timestamp forward.
VoltDB / H-Store
VoltDB (and its academic predecessor H-Store) achieves serializability through actual serial execution. Each data partition runs on a single thread, executing stored procedures one at a time. This eliminates all locking overhead and concurrency anomalies. Multi-partition transactions are coordinated via a two-phase protocol. VoltDB targets OLTP workloads where transactions are short (sub-millisecond) and data fits in RAM.
| Aspect | Description |
|---|---|
| Safety vs Performance | Serializability prevents all concurrency anomalies but reduces throughput compared to weaker isolation levels. Under read-committed or snapshot isolation, more transactions can proceed concurrently because they hold fewer/shorter locks or accept more types of concurrent access. The performance penalty depends on the workload: read-heavy workloads see less impact than write-heavy contended workloads. |
| Pessimistic (2PL) vs Optimistic (SSI) | 2PL blocks transactions proactively -- if a conflict might occur, the transaction waits. SSI allows transactions to proceed optimistically and aborts them if a conflict is detected at commit. 2PL has predictable latency but risks deadlocks and lock waits. SSI has better throughput under low contention but higher abort rates under high contention. |
| Generality vs Restrictions | Actual serial execution provides serializability with zero overhead but restricts transactions to short, non-interactive stored procedures that fit in memory. 2PL and SSI support arbitrary interactive transactions but add overhead. The choice depends on whether your workload fits the serial execution constraints. |
| Abort Rate | SSI transactions may be aborted at commit time if conflicts are detected. Applications must handle serialization failures by retrying the entire transaction. Under high contention (many transactions accessing the same data), abort rates can reach 10-30%, significantly reducing effective throughput. 2PL avoids this by blocking instead of aborting, at the cost of longer wait times. |
PostgreSQL's Serializable Snapshot Isolation (SSI)
Scenario
PostgreSQL's snapshot isolation (REPEATABLE READ) prevented most anomalies but allowed write skew, which could violate application invariants. Adding full serializability via 2PL would have required fundamental changes to PostgreSQL's MVCC architecture and significantly reduced concurrent read performance. The challenge was to provide serializability without sacrificing PostgreSQL's excellent read throughput.
Solution
Michael Cahill, Uwe Rohm, and Alan Fekete developed SSI, which layers conflict detection on top of existing snapshot isolation. PostgreSQL tracks SIRead locks (predicate locks on what transactions have read) and detects dangerous structures -- specifically, cycles of rw-antidependencies where T1 reads something T2 wrote and T2 reads something T1 wrote (or a third transaction is involved). When such a structure is detected, PostgreSQL aborts one of the transactions with a serialization_failure error, which the application must retry.
Outcome
SSI shipped in PostgreSQL 9.1 (2011) and became the first production implementation of serializable isolation that did not use locking for reads. Benchmarks showed that SSI achieved 90-95% of snapshot isolation's throughput under low contention, with degradation only under high write contention. The approach was adopted by CockroachDB and influenced other distributed databases. SSI demonstrated that serializability does not require the severe performance penalties traditionally associated with 2PL.
See Serializability in action
Explore system design templates that use serializability and run traffic simulations to see how these concepts perform under real load.
Browse Templates1Which anomaly can occur under snapshot isolation but not under serializability?
2How does SSI (Serializable Snapshot Isolation) achieve serializability without blocking reads?