Loading learning content...
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.
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:
Important Terminology Confusion:
Many databases conflate these terms:
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!
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?
SI and Serializable isolation use fundamentally different mechanisms, leading to different performance characteristics.
Snapshot Isolation Mechanism:
2PL Serializable Mechanism:
SSI Serializable Mechanism:
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:
Conflict Detection:
At commit time, SSI checks if the committing transaction is part of a "dangerous structure":
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 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:
SSI Costs:
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.
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:
Usage:
-- Snapshot Isolation (allows write skew)
BEGIN ISOLATION LEVEL REPEATABLE READ;
-- True Serializable (prevents write skew)
BEGIN ISOLATION LEVEL SERIALIZABLE;
Key Points:
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.
The choice between SI and Serializable depends on your application's requirements, workload characteristics, and tolerance for complexity.
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.
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:
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))
Potential Issues:
Migrating Serializable → SI:
This is the 'dangerous' direction—you're removing protection.
Steps:
Potential Issues:
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.
The choice between Snapshot Isolation and Serializable is one of the most important decisions in database application design. Each level offers different trade-offs between correctness guarantees, performance characteristics, and implementation complexity.
What's Next:
The final page explores Practical Usage of Snapshot Isolation, covering real-world patterns, optimization strategies, monitoring techniques, and best practices for deploying SI-based systems in production.
You now understand the comprehensive trade-offs between Snapshot Isolation and Serializable isolation—from theoretical foundations through practical implementation differences, performance characteristics, database-specific behaviors, and decision frameworks for choosing the right level for your applications.