Database Isolation Levels
What is Isolation?
Section titled “What is Isolation?”Isolation is an ACID property that ensures concurrent transactions don’t interfere with each other. Each transaction sees a consistent snapshot of the data, even when other transactions are running simultaneously.
The Isolation Levels Spectrum
Section titled “The Isolation Levels Spectrum”Isolation levels form a spectrum from weakest (most concurrency, most anomalies) to strongest (least concurrency, no anomalies):
Level 1: Read Uncommitted
Section titled “Level 1: Read Uncommitted”Read Uncommitted is the lowest isolation level. Transactions can read uncommitted data from other transactions.
The Problem: Dirty Reads
Section titled “The Problem: Dirty Reads”Example: Transaction 1 updates balance to 200 but hasn’t committed. Transaction 2 reads 200. Then Transaction 1 rolls back. Transaction 2 saw dirty (uncommitted) data that never actually existed!
When to Use: Almost never. Only for read-only analytics where accuracy isn’t critical.
Level 2: Read Committed
Section titled “Level 2: Read Committed”Read Committed prevents dirty reads. Transactions can only read committed data.
How It Works
Section titled “How It Works”Key Characteristic: Each read sees the latest committed value at the time of the read.
The Problem: Non-Repeatable Reads
Section titled “The Problem: Non-Repeatable Reads”Example: Transaction 1 reads balance = 100. Transaction 2 updates it to 200 and commits. Transaction 1 reads again and gets 200. Same transaction, different values!
When to Use: Default in most databases (PostgreSQL, SQL Server). Good balance of consistency and performance.
Level 3: Repeatable Read
Section titled “Level 3: Repeatable Read”Repeatable Read prevents dirty reads and non-repeatable reads. A transaction sees a consistent snapshot throughout its lifetime.
How It Works
Section titled “How It Works”Key Characteristic: Transaction sees the same data on repeated reads, even if other transactions commit changes.
The Problem: Phantom Reads
Section titled “The Problem: Phantom Reads”Example: Transaction 1 counts active orders (gets 5). Transaction 2 inserts a new active order and commits. Transaction 1 counts again (gets 6). A phantom row appeared!
When to Use: When you need consistent reads of the same rows (e.g., calculating totals, validating constraints).
Level 4: Serializable
Section titled “Level 4: Serializable”Serializable is the highest isolation level. Transactions execute as if they ran one at a time (serially).
How It Works
Section titled “How It Works”Key Characteristic: Database ensures transactions produce the same result as if they ran serially (one after another).
When to Use: Critical financial transactions, inventory management, any scenario where perfect consistency is required.
Isolation Level Comparison
Section titled “Isolation Level Comparison”| Isolation Level | Dirty Read | Non-Repeatable Read | Phantom Read | Concurrency | Use Case |
|---|---|---|---|---|---|
| Read Uncommitted | Possible | Possible | Possible | Highest | Almost never |
| Read Committed | Prevented | Possible | Possible | High | Default (most DBs) |
| Repeatable Read | Prevented | Prevented | Possible | ⚠️ Medium | Consistent reads |
| Serializable | Prevented | Prevented | Prevented | Lowest | Critical operations |
Real-World Examples
Section titled “Real-World Examples”Understanding how major companies use isolation levels helps illustrate their practical importance:
Read Committed: E-commerce Inventory Management
Section titled “Read Committed: E-commerce Inventory Management”The Challenge: E-commerce platforms need to prevent overselling. When multiple users try to buy the last item simultaneously, only one should succeed.
The Solution: Amazon uses Read Committed isolation:
- Scenario: Product has 1 item left. Two users try to buy it simultaneously.
- Read Committed: Each transaction sees committed data only. First transaction commits → second sees 0 items → purchase fails.
- Prevents: Overselling, negative inventory
Example:
- T1: User A reads inventory (1 item) → reserves item
- T2: User B reads inventory (1 item) → tries to reserve
- T1: Commits → inventory = 0
- T2: Tries to commit → fails (inventory already 0)
Impact: Zero overselling incidents. Inventory always accurate. Critical for e-commerce.
Repeatable Read: Financial Reporting
Section titled “Repeatable Read: Financial Reporting”The Challenge: Financial reports need consistent snapshots. A report showing account balances shouldn’t change mid-generation.
The Solution: Banks use Repeatable Read for reporting:
- Scenario: Generate monthly statement. Account has multiple transactions during report generation.
- Repeatable Read: Report sees consistent snapshot. All reads within transaction see same data.
- Prevents: Inconsistent reports, mid-transaction changes
Example:
- T1: Begin report generation
- T1: Read balance at start of month: $1000
- T2: Process transaction: $100 → $900
- T1: Read balance at end of month: Still sees $1000 (consistent snapshot)
- T1: Commit → Report shows consistent data
Impact: Consistent financial reports. No mid-generation changes. Critical for compliance.
Serializable: Ticket Booking Systems
Section titled “Serializable: Ticket Booking Systems”The Challenge: Ticket booking systems need to prevent double-booking. When multiple users try to book the same seat, only one should succeed.
The Solution: Airlines use Serializable isolation:
- Scenario: Last seat on flight. Two users try to book simultaneously.
- Serializable: Transactions execute serially. One completes before other starts.
- Prevents: Double-booking, race conditions
Example:
- T1: User A checks seat availability (available) → locks seat
- T2: User B checks seat availability (waits for T1)
- T1: Books seat → commits
- T2: Sees seat unavailable → booking fails
Impact: Zero double-booking incidents. All bookings are valid. Critical for ticket systems.
Read Uncommitted: Analytics and Monitoring
Section titled “Read Uncommitted: Analytics and Monitoring”The Challenge: Analytics systems need real-time data but can tolerate some inconsistency. Performance matters more than perfect accuracy.
The Solution: Analytics platforms use Read Uncommitted for dashboards:
- Scenario: Real-time dashboard showing user count, page views, etc.
- Read Uncommitted: Reads uncommitted data for speed. Some inconsistency acceptable.
- Trade-off: Speed vs accuracy
Example:
- T1: User creates account (uncommitted: +1 user)
- Dashboard: Reads uncommitted count (shows +1 immediately)
- T1: Rolls back (account creation failed)
- Dashboard: Eventually corrects (shows accurate count)
Impact: Real-time dashboards. Fast updates. Acceptable for analytics (not financial data).
PostgreSQL Default: Read Committed
Section titled “PostgreSQL Default: Read Committed”The Challenge: Most applications need balance between consistency and performance.
The Solution: PostgreSQL defaults to Read Committed:
- Why: Good balance. Prevents dirty reads (critical) but allows good concurrency.
- Use case: Most web applications, APIs, general-purpose systems
Example: User updates profile while another user reads it:
- T1: Update profile (uncommitted changes)
- T2: Read profile (sees old committed data, not uncommitted)
- T1: Commit
- T2: Next read sees new data
Impact: Used by millions of applications. Good default for most use cases.
Example 1: Bank Balance Check (Read Committed)
Section titled “Example 1: Bank Balance Check (Read Committed)”Isolation Level: Read Committed
Why: User wants to see current balance, doesn’t need perfect consistency for a simple read.
Example 2: Inventory Count (Repeatable Read)
Section titled “Example 2: Inventory Count (Repeatable Read)”Isolation Level: Repeatable Read
Why: Need to read inventory multiple times and ensure it doesn’t change between reads.
Example 3: Financial Transfer (Serializable)
Section titled “Example 3: Financial Transfer (Serializable)”Isolation Level: Serializable
Why: Financial transactions require perfect consistency. Can’t have race conditions or anomalies.
LLD ↔ HLD Connection
Section titled “LLD ↔ HLD Connection”How isolation levels affect your code design:
Choosing Isolation Level
Section titled “Choosing Isolation Level”Deep Dive: Advanced Isolation Considerations
Section titled “Deep Dive: Advanced Isolation Considerations”Deadlocks: The Hidden Cost of High Isolation
Section titled “Deadlocks: The Hidden Cost of High Isolation”Deadlock occurs when two transactions wait for each other’s locks indefinitely.
Deadlock Scenario
Section titled “Deadlock Scenario”Production Impact:
- Frequency: Deadlocks occur ~0.01-0.1% of transactions in high-concurrency systems
- Detection: Databases detect deadlocks automatically (usually less than 1 second)
- Recovery: One transaction aborted, retried by application
- Cost: Aborted transaction wasted work, retry overhead
Deadlock Prevention Strategies
Section titled “Deadlock Prevention Strategies”Strategy 1: Lock Ordering
Pattern: Always acquire locks in the same order.
Example:
def transfer_money(self, from_id, to_id, amount): # Always lock lower ID first first_id = min(from_id, to_id) second_id = max(from_id, to_id)
with self.lock(first_id): with self.lock(second_id): # Transfer logicBenefit: Prevents circular wait → no deadlocks
Strategy 2: Lock Timeout
Pattern: Set timeout on lock acquisition.
Example:
def transfer_money(self, from_id, to_id, amount): try: # Try to acquire locks with timeout if not self.try_lock(from_id, timeout=5): raise LockTimeout("Could not acquire lock") if not self.try_lock(to_id, timeout=5): raise LockTimeout("Could not acquire lock") # Transfer logic finally: self.unlock(from_id) self.unlock(to_id)Benefit: Fails fast instead of deadlocking
Strategy 3: Reduce Isolation Level
Pattern: Use lower isolation when possible.
Example:
- Serializable: Highest deadlock risk
- Repeatable Read: Medium deadlock risk
- Read Committed: Lower deadlock risk (fewer locks)
Trade-off: Lower isolation = fewer deadlocks but more anomalies
Isolation Level Performance Impact
Section titled “Isolation Level Performance Impact”Lock Contention Analysis
Section titled “Lock Contention Analysis”Read Committed:
- Locks: Short-lived (released after read)
- Contention: Low
- Throughput: High (10K-50K TPS)
- Deadlock Risk: Low
Repeatable Read:
- Locks: Held for transaction duration
- Contention: Medium
- Throughput: Medium (5K-20K TPS)
- Deadlock Risk: Medium
Serializable:
- Locks: Range locks, held for transaction duration
- Contention: High
- Throughput: Low (1K-5K TPS)
- Deadlock Risk: High
Production Benchmark (PostgreSQL):
| Isolation Level | Read Latency | Write Latency | Throughput | Deadlock Rate |
|---|---|---|---|---|
| Read Committed | 5ms | 10ms | 20K TPS | 0.01% |
| Repeatable Read | 8ms | 15ms | 10K TPS | 0.05% |
| Serializable | 15ms | 30ms | 3K TPS | 0.2% |
Key Insight: Each isolation level increase costs 2-3x in performance.
Snapshot Isolation: The Middle Ground
Section titled “Snapshot Isolation: The Middle Ground”Snapshot Isolation is used by many databases (PostgreSQL, MySQL InnoDB) as their default “Repeatable Read” implementation.
How it works:
- Each transaction sees a snapshot of data at transaction start
- Writes create new versions (multi-version concurrency control - MVCC)
- No locks for reads (read from snapshot)
- Locks only for writes (prevent conflicts)
Benefits:
- No read locks: Readers don’t block writers
- No write locks: Writers don’t block readers (until commit)
- Better concurrency: Higher throughput than locking
Trade-offs:
- ⚠️ Storage overhead: Must store multiple versions
- ⚠️ Vacuum required: Old versions must be cleaned up
Production Example: PostgreSQL
- Uses MVCC for all isolation levels
- Vacuum process: Runs periodically to clean old versions
- Performance: 2-5x better than locking-based isolation
Read Phenomena: Detailed Analysis
Section titled “Read Phenomena: Detailed Analysis”Dirty Read: Real-World Impact
Section titled “Dirty Read: Real-World Impact”Scenario: Financial reporting
Impact:
- Financial reports: Wrong numbers → regulatory issues
- Analytics: Incorrect insights → bad decisions
- Auditing: Compliance violations
Prevention: Read Committed or higher
Non-Repeatable Read: Business Logic Impact
Section titled “Non-Repeatable Read: Business Logic Impact”Scenario: Inventory validation
Impact:
- Overselling: Sell more than available
- Double-booking: Book same resource twice
- Race conditions: Business logic fails
Prevention: Repeatable Read or higher
Phantom Read: Aggregation Impact
Section titled “Phantom Read: Aggregation Impact”Scenario: Count operations
Impact:
- Statistics: Wrong averages, totals
- Reports: Incorrect aggregations
- Business metrics: Wrong KPIs
Prevention: Serializable (only level that prevents phantoms)
Isolation Level Selection: Production Guidelines
Section titled “Isolation Level Selection: Production Guidelines”Decision Framework
Section titled “Decision Framework”Production Rules:
- Start with Read Committed (default, works for 90% of cases)
- Upgrade to Repeatable Read if you read same row twice
- Upgrade to Serializable only if phantoms cause problems
- Monitor deadlock rate - if high, consider lowering isolation
Database-Specific Isolation Implementations
Section titled “Database-Specific Isolation Implementations”PostgreSQL Isolation Levels
Section titled “PostgreSQL Isolation Levels”Read Committed (Default):
- Uses MVCC (Multi-Version Concurrency Control)
- Snapshot: Taken at start of each statement
- Locks: Only for writes
- Performance: Excellent
Repeatable Read:
- Uses MVCC with transaction-level snapshot
- Snapshot: Taken at transaction start
- Locks: Only for writes
- Note: Actually prevents phantoms (stricter than SQL standard!)
Serializable:
- Uses Serializable Snapshot Isolation (SSI)
- Detection: Detects serialization conflicts
- Abort: Aborts conflicting transactions
- Performance: Good (better than locking-based)
MySQL InnoDB Isolation Levels
Section titled “MySQL InnoDB Isolation Levels”Read Committed:
- Uses MVCC
- Snapshot: Per statement
- Performance: Good
Repeatable Read (Default):
- Uses MVCC with transaction snapshot
- Gap locks: Prevents phantoms (stricter than standard!)
- Performance: Good
Serializable:
- Uses locking (not MVCC)
- Locks: All reads acquire shared locks
- Performance: Poor (much slower)
Key Difference: MySQL’s Repeatable Read is stricter than PostgreSQL’s!
Production Best Practices
Section titled “Production Best Practices”Practice 1: Keep Transactions Short
Section titled “Practice 1: Keep Transactions Short”Rule: Minimize transaction duration to reduce lock contention.
Bad:
# BAD: Long transactiondef process_order(order): with transaction(): validate_order(order) # External API call (slow!) reserve_inventory(order) charge_payment(order) # Transaction held during slow API callGood:
# GOOD: Short transactiondef process_order(order): # Do slow work outside transaction validation_result = validate_order(order) # Outside transaction
# Short transaction with transaction(): reserve_inventory(order) charge_payment(order)Benefit: Reduces lock time → fewer deadlocks, better throughput
Practice 2: Use Appropriate Isolation Per Operation
Section titled “Practice 2: Use Appropriate Isolation Per Operation”Pattern: Different isolation levels for different operations.
Example:
class OrderService: def get_order(self, order_id): # Read Committed - simple read return self.db.read(order_id, isolation='READ_COMMITTED')
def validate_inventory(self, product_id): # Repeatable Read - need consistent reads return self.db.read(product_id, isolation='REPEATABLE_READ')
def transfer_money(self, from_id, to_id, amount): # Serializable - critical operation return self.db.transfer(from_id, to_id, amount, isolation='SERIALIZABLE')Benefit: Optimize each operation for its needs
Practice 3: Monitor Isolation-Level Metrics
Section titled “Practice 3: Monitor Isolation-Level Metrics”What to Monitor:
- Deadlock rate: Should be less than 0.1%
- Lock wait time: P95 should be less than 100ms
- Transaction duration: P95 should be less than 1s
- Rollback rate: High rate indicates problems
Production Monitoring:
class IsolationMonitor: def track_transaction(self, isolation_level, duration, deadlocked): self.metrics.increment(f'transaction.{isolation_level}.total') self.metrics.histogram(f'transaction.{isolation_level}.duration', duration)
if deadlocked: self.metrics.increment(f'transaction.{isolation_level}.deadlock') self.alert_on_deadlock(isolation_level)Key Takeaways
Section titled “Key Takeaways”What’s Next?
Section titled “What’s Next?”Now that you understand isolation levels, let’s explore how to scale databases to handle more load:
Next up: Scaling Databases — Learn about read replicas, connection pooling, and scaling strategies.