Snapshot Isolation: Consistent Views for Concurrent Transactions
Snapshot Isolation vs Serializable
Read aloud is unavailable in this browser.
The Isolation Spectrum
We've explored snapshot isolation in depth—its elegant snapshot-based reads, its high concurrency, and its Achilles heel: write skew. We've also seen that SERIALIZABLE isolation can prevent write skew through mechanisms like SSI. But how do these isolation levels truly compare?
This page provides a comprehensive comparison between Snapshot Isolation (SI) and Serializable isolation, examining their theoretical foundations, anomaly prevention capabilities, performance characteristics, and practical trade-offs. Understanding this comparison is essential for making informed decisions about which isolation level to use in your applications.
By the end of this page, you will understand: the theoretical relationship between SI and serializability; exactly which anomalies each level prevents; how performance differs under various workloads; how different databases implement these levels; and guidelines for choosing the appropriate isolation level for your application.
Theoretical Foundations
To compare SI and Serializable, we must first understand their theoretical foundations and where they sit in the isolation hierarchy.
Serializability: The Gold Standard
Serializability is the strongest commonly-used isolation level. A schedule is serializable if its effects are equivalent to some serial execution of the same transactions. This means no anomalies are possible—every concurrent execution could have happened as a sequence of non-overlapping transactions.
Formal Definition:
A schedule $S$ is serializable if there exists a serial schedule $S'$ with the same transactions where: for all data items $x$, if transaction $T_i$ reads or writes $x$ in $S$, the final value and all intermediate values of $x$ are identical to those in $S'$.
Snapshot Isolation: A Different Approach
Snapshot Isolation provides a different guarantee: each transaction sees a consistent snapshot of the database as it existed at the transaction's start time. Writes are checked for conflicts using First-Committer-Wins. SI was developed as a practical alternative to serializability that provides high concurrency while preventing most common anomalies.
Where SI Fits in the Hierarchy:
Snapshot Isolation is not part of the original SQL standard's four isolation levels. It sits between REPEATABLE READ and SERIALIZABLE in terms of anomaly prevention:
- SI prevents all anomalies that REPEATABLE READ prevents
- SI also prevents phantom reads for read-only transactions
- SI does NOT guarantee serializability (allows write skew)
- SI uses a fundamentally different mechanism (snapshots vs locks)
Important Terminology Confusion:
Many databases conflate these terms:
- PostgreSQL calls SI "REPEATABLE READ" and offers true SERIALIZABLE via SSI
- Oracle calls SI "SERIALIZABLE" (misleadingly)
- MySQL InnoDB's "REPEATABLE READ" uses SI plus gap locking
Always verify what your specific database actually provides at each isolation level.
Oracle's 'SERIALIZABLE' level is actually Snapshot Isolation and does NOT prevent write skew. PostgreSQL's 'SERIALIZABLE' does prevent write skew via SSI. Don't assume isolation level names mean the same thing across databases!
Anomaly Prevention: A Detailed Comparison
Let's systematically compare which anomalies are prevented by each isolation level.
| Anomaly | Snapshot Isolation | Serializable (2PL) | Serializable (SSI) | Notes |
|---|---|---|---|---|
| Dirty Read | ✅ Prevented | ✅ Prevented | ✅ Prevented | Both only see committed data |
| Non-Repeatable Read | ✅ Prevented | ✅ Prevented | ✅ Prevented | SI via snapshot; 2PL via locks |
| Phantom Read (read-only) | ✅ Prevented | ✅ Prevented | ✅ Prevented | SI snapshot includes all rows |
| Phantom Read (write) | ⚠️ Partial | ✅ Prevented | ✅ Prevented | SI may allow phantoms affecting writes |
| Lost Update | ✅ Prevented | ✅ Prevented | ✅ Prevented | SI via FCW; 2PL via locks |
| Read Skew | ✅ Prevented | ✅ Prevented | ✅ Prevented | SI snapshot is consistent |
| Write Skew | ❌ Allowed | ✅ Prevented | ✅ Prevented | SI's key weakness |
| Serialization Anomaly | ❌ Possible | ✅ Prevented | ✅ Prevented | By definition |
Understanding Each Anomaly:
Dirty Read: Reading uncommitted data. SI prevents this because snapshots only include committed transactions.
Non-Repeatable Read: Reading the same row twice and getting different values. SI prevents this because the snapshot is immutable.
Phantom Read: A range query returns different rows on re-execution. For read-only transactions, SI's snapshot prevents this. For transactions that write based on range query results, phantoms can cause problems similar to write skew.
Lost Update: Two transactions read the same row, both modify it, one overwrites the other. SI's FCW ensures only the first committer's update survives; the second must retry.
Read Skew: Reading two related items that are inconsistent with each other. SI's snapshot ensures all reads see the same consistent state.
Write Skew: Reading overlapping data, writing different items, violating a spanning constraint. This is the one SI does NOT prevent.
Write skew is the ONLY standard anomaly that separates SI from serializability. If your application has no constraints spanning multiple rows, SI is effectively as strong as serializable for your use case. The question is: do you have such constraints?
Implementation Mechanisms
SI and Serializable isolation use fundamentally different mechanisms, leading to different performance characteristics.
Snapshot Isolation Mechanism:
- Snapshot on start: Transaction captures a point-in-time view
- Read from snapshot: All reads see the frozen snapshot state
- Write directly: Writes create new versions in current state
- FCW on commit: Check for write-write conflicts on same rows
2PL Serializable Mechanism:
- Acquire locks: Get shared locks for reads, exclusive for writes
- Hold locks: Locks held until transaction completes (two-phase)
- Block on conflict: Transactions wait for conflicting locks
- Release at end: All locks released on commit/abort
SSI Serializable Mechanism:
- Snapshot + tracking: Take snapshot AND track read/write sets
- SIREAD locks: Virtual locks record reads without blocking
- Detect cycles: Check for rw-antidependency cycles
- Abort on danger: Abort if dangerous structure detected
- •Non-blocking reads — Readers never wait for writers
- •Non-blocking writes to different rows — No lock waits for distinct data
- •Version chains — Storage overhead for multiple versions
- •FCW aborts — Write conflicts cause retries (not waits)
- •Consistent reads — Always see point-in-time snapshot
- •Blocking reads/writes — Lock conflicts cause waits
- •Deadlock possible — Circular wait detection needed
- •Lock table overhead — Memory for lock management
- •No aborts for conflict — Wait instead of retry
- •Current value reads — See latest committed data
SSI Combines Both Approaches:
SSI uses SI as its base (snapshot reads, FCW) but adds dependency tracking to detect serializability violations.
SIREAD Locks:
Unlike real locks, SIREAD locks don't block. They're record-keeping mechanisms that track:
- What transactions read which data
- What data was written after being read by another transaction
Conflict Detection:
At commit time, SSI checks if the committing transaction is part of a "dangerous structure":
- A transaction with rw-antidependency from a committed transaction
- AND rw-antidependency to another concurrent transaction
If detected, the transaction is aborted with a serialization failure. The application must retry.
SSI trades blocking for aborts. 2PL makes transactions wait. SSI lets them run and aborts if there's a problem. For workloads with few conflicts, SSI is often faster. For workloads with frequent conflicts, waiting (2PL) may be more efficient than retrying (SSI).
Performance Characteristics
Performance differences between SI and Serializable depend heavily on workload characteristics. Let's analyze different scenarios.
| Workload | SI Performance | 2PL Serializable | SSI Serializable |
|---|---|---|---|
| Read-heavy, few conflicts | Excellent (no blocking) | Good (shared locks) | Excellent (no blocking) |
| Write-heavy, different rows | Excellent (no conflicts) | Poor (lock overhead) | Good (tracking overhead) |
| Write-heavy, same rows | Good (FCW retries) | Good (blocking) | Good (retries + tracking) |
| Mixed, high contention | Good (FCW handles) | Poor (blocking, deadlocks) | Fair (many retries) |
| Long transactions | Good (snapshots cheap) | Poor (lock duration) | Fair (SIREAD retention) |
| Short OLTP | Excellent | Good | Very Good |
SI Performance Advantages:
-
Readers never block writers and vice versa: This is the fundamental advantage. Long-running reports don't hold up short OLTP transactions.
-
No deadlock between readers and writers: Deadlocks can still occur between writers, but the reader-writer deadlock class is eliminated.
-
Predictable read latency: Reads always succeed immediately with snapshot data. No lock waits.
-
High concurrency for multi-row reads: Reading multiple tables or rows for complex queries doesn't accumulate locks.
Serializable Performance Costs:
2PL Costs:
- Lock acquisition and release overhead
- Lock table memory
- Blocking wait time
- Deadlock detection and resolution
- Lock escalation overhead
SSI Costs:
- SIREAD lock tracking overhead
- Predicate lock management
- Conflict detection at commit
- Higher abort rate than SI
- SIREAD lock retention for active transactions
123456789101112131415161718192021222324252627282930
# Simplified benchmark comparison (illustrative numbers) WORKLOAD: 1000 concurrent transactions - 80% read-only (10 rows each) - 20% read-write (read 5 rows, write 1 row) - 5% contention rate on hot rows SNAPSHOT ISOLATION RESULTS: Throughput: 15,000 tx/sec Avg read latency: 2ms Avg write latency: 5ms Abort rate: 0.5% (FCW conflicts) Deadlocks: 0 2PL SERIALIZABLE RESULTS: Throughput: 8,000 tx/sec Avg read latency: 3ms (lock acquisition) Avg write latency: 12ms (blocking waits) Abort rate: 0.2% (deadlock victims) Deadlocks: 20/sec (detected and resolved) SSI SERIALIZABLE RESULTS: Throughput: 12,000 tx/sec Avg read latency: 2.5ms (SIREAD tracking) Avg write latency: 6ms Abort rate: 2% (serialization failures + FCW) Deadlocks: 0 # Key observation: SSI is between SI and 2PL in overhead# Trade-off: SSI has higher abort rate but lower latencyWhen comparing abort rates, remember that aborts have costs: the aborted work is wasted, and the retry consumes additional resources. High abort rates can negate SSI's latency advantages. Monitor and tune based on actual abort rates in your workload.
How Different Databases Implement These Levels
Understanding that isolation level names mean different things in different databases is crucial for portability and correctness.
| Database | REPEATABLE READ | SERIALIZABLE | True Serializable? |
|---|---|---|---|
| PostgreSQL | Snapshot Isolation | SSI | Yes (SERIALIZABLE level) |
| Oracle | N/A (use SERIALIZABLE) | Snapshot Isolation | No (misleadingly named) |
| MySQL InnoDB | SI + Gap Locking | 2PL | Yes (SERIALIZABLE level) |
| SQL Server | Lock-based RR | 2PL or SI+SSI | Yes (both methods available) |
| CockroachDB | SI | SSI | Yes (SERIALIZABLE level) |
| TiDB | SI | Not available | No (optimistic SI only) |
PostgreSQL's Approach:
- REPEATABLE READ: Actual Snapshot Isolation. Prevents phantoms for reads but allows write skew.
- SERIALIZABLE: SSI-based. Adds rw-antidependency tracking to detect write skew.
Usage:
-- Snapshot Isolation (allows write skew)
BEGIN ISOLATION LEVEL REPEATABLE READ;
-- True Serializable (prevents write skew)
BEGIN ISOLATION LEVEL SERIALIZABLE;
Key Points:
- SSI requires no schema changes
- Serialization failures must be handled with retries
- SSI has ~5-10% overhead over SI for typical workloads
- Long-running read transactions can accumulate SIREAD locks
Never assume isolation level names have consistent meaning. Test your specific database version with write skew scenarios to verify whether your 'SERIALIZABLE' actually prevents write skew. Documentation can be outdated or unclear.
Choosing the Right Isolation Level
The choice between SI and Serializable depends on your application's requirements, workload characteristics, and tolerance for complexity.
- •No multi-row constraints exist — If your application has no constraints spanning multiple rows, SI provides the same guarantees as serializable
- •Constraints enforced by database — UNIQUE constraints, foreign keys, and triggers can catch violations regardless of isolation level
- •Read-heavy workload — SI's non-blocking reads excel when most transactions are read-only
- •Long-running reports — Reports can run without blocking or being blocked by OLTP workloads
- •High concurrency required — SI's minimal blocking maximizes throughput
- •Application handles conflicts — If retrying FCW failures is acceptable and rare
- •Application-level constraints span rows — Multi-row invariants (like 'at least one on-call') require serializable
- •Correctness is paramount — Financial systems, inventory management where bugs are costly
- •Complex business logic — When it's hard to reason about all possible interleavings
- •Audit requirements — When you must be able to explain execution as a serial schedule
- •Simpler application code — Serializable lets you reason sequentially without concurrency concerns
- •Short transactions — SSI overhead is minimal for quick OLTP transactions
Decision Framework:
1. Identify all constraints in your application
- Schema constraints (UNIQUE, FK, CHECK)
- Application constraints (business rules)
2. Categorize application constraints
- Single-row constraints → SI is sufficient
- Multi-row constraints → Need Serializable OR explicit locking
3. Evaluate constraint enforcement options
- Can constraint be a DB trigger? → SI + trigger
- Can we use SELECT FOR UPDATE? → SI + locking
- Too complex? → Use Serializable
4. Consider performance requirements
- High read concurrency needed? → Prefer SI/SSI
- Low contention? → SSI overhead is acceptable
- High contention? → Consider 2PL or SI + explicit locking
5. Choose and test
- Test with production-like workloads
- Measure abort rates and latency
- Verify constraint enforcement
If in doubt, start with SERIALIZABLE. It's correct by default. Once you understand your workload and can prove that write skew isn't a concern for specific transactions, you can selectively use lower isolation levels for those transactions. Correctness first, performance second.
Migrating Between Isolation Levels
Changing isolation levels in an existing application requires careful planning. Let's examine strategies for both directions.
Migrating SI → Serializable:
This is the 'safer' direction—you're adding protection, not removing it.
Steps:
- Update connection configuration to use SERIALIZABLE
- Implement retry logic for serialization failures:
MAX_RETRIES = 3 for attempt in range(MAX_RETRIES): try: with connection.begin(): do_transaction_work() break # Success except SerializationFailure: if attempt == MAX_RETRIES - 1: raise time.sleep(random.uniform(0.01, 0.1)) - Monitor abort rates and adjust if too high
- Test under production-like load
Potential Issues:
- Higher abort rates require retry logic everywhere
- Some transactions may retry repeatedly under high contention
- SSI's SIREAD tracking may increase memory pressure
Migrating Serializable → SI:
This is the 'dangerous' direction—you're removing protection.
Steps:
- Audit all constraints: Identify every application constraint
- Categorize by risk: Which constraints span multiple rows?
- Implement alternatives for risky constraints:
- Add database triggers
- Use SELECT FOR UPDATE
- Add explicit lock rows
- Keep SERIALIZABLE for these specific transactions
- Test exhaustively: Write tests that exercise concurrent scenarios
- Monitor for violations: Log and alert on constraint violations
Potential Issues:
- Risk of introducing write skew bugs
- Need comprehensive test coverage
- May need to mix isolation levels per transaction
- Subtle bugs may only appear under high concurrency
Some applications use different isolation levels for different transactions. This is valid but adds complexity. Document clearly which transactions use which levels and why. Ensure developers understand the implications when adding new transactions.
KEEP LEARNING
There’s more to understand.
You’re reading the introduction. Create a free account to read the complete lesson, save notes and keep track of your progress.