REPEATABLE READ Phantom Inventory — DBMS Transactions
REPEATABLE READ does not prevent phantom reads — new inserts appear in range scans, causing $50k stock discrepancy.
- ACID defines the contract: Atomicity, Consistency, Isolation, Durability — violations cause silent data corruption
- Isolation levels trade safety for throughput: READ COMMITTED is the production default for most web apps
- MVCC keeps reads from blocking writes, but phantom reads still sneak through in REPEATABLE READ
- Deadlocks are an application design failure, not a database bug — always lock resources in a consistent order
- The biggest mistake: assuming your app's isolation level matches the database default (they often don't)
Think of a transaction like a bank transfer. You want to send $100 from your account to a friend's. The database must either do both steps (deduct and add) or none. If the server dies after deducting but before adding, your money shouldn't vanish. That's ACID in action.
ACID in Practice: Beyond the Acronym
While every textbook defines ACID, seeing it handle a failure in real-time clarifies why we pay the performance tax for a relational engine. Atomicity is the 'all-or-nothing' guarantee, but Durability is the 'promise'—ensuring that once a COMMIT is acknowledged, even a total power failure won't revert the change.
Isolation Levels and Read Phenomena
Isolation levels are a slider between 'Perfect Safety' and 'Maximum Speed.' As you lower the level, you introduce specific anomalies: 1. Dirty Reads: Reading data that hasn't been committed yet. 2. Non-Repeatable Reads: A row changes its value while you're still looking at it. 3. Phantom Reads: New rows appear in a range query because another transaction inserted them.
Deadlocks: Detection and Prevention
A deadlock occurs when Transaction A holds Lock 1 and wants Lock 2, while Transaction B holds Lock 2 and wants Lock 1. They will wait forever. Most modern RDBMS (Postgres, MySQL) run a background 'Deadlock Detector' thread that identifies these cycles and kills the 'cheapest' transaction to break the loop.
- Each transaction (car) holds a resource (position in the roundabout).
- Each wants another resource that is held by another car.
- The database traffic cop (deadlock detector) picks one car to reverse out.
- That transaction is rolled back and must retry.
- You can avoid the gridlock by enforcing a consistent direction of travel (lock order).
MVCC: How Isolation Levels Actually Work
Multi-Version Concurrency Control (MVCC) allows each transaction to see a snapshot of the data at the start of the transaction (or at the statement level, depending on isolation). Instead of locking every row a reader might touch, the database keeps multiple versions of each row. When a transaction writes, it creates a new version; concurrent readers see the old version until the writer commits. This is why PostgreSQL can run thousands of concurrent SELECTs without blocking — readers never block writers, and writers never block readers (unless they conflict).
Transaction Management in Practice: Application-Level Patterns
Real-world apps rarely use raw SQL transactions. Frameworks like Spring Boot, Hibernate, and Entity Framework manage transaction boundaries declaratively. You need to understand: - Transaction propagation: REQUIRED, REQUIRES_NEW, NESTED — when does a new transaction start? - Isolation level override: @Transactional(isolation = Isolation.REPEATABLE_READ) can clash with database defaults. - Transactional outbox pattern for reliable messaging. - Handling rollback-only exceptions: sometimes a caught exception still marks the transaction for rollback. - Setting timeout and read-only hints to optimize connection usage.
@Transactional method and do not rethrow it, the transaction is still marked rollback-only. The commit will fail with Transaction rolled back because it has been marked as rollback-only. Always rethrow the exception or use TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() deliberately.@Transactional on a private method — it's ignored because the proxy can't intercept it.@Transactional method from within the same class bypasses the proxy.@Transactional from another bean to trigger the proxy.@Transactional are syntactic sugar — they still rely on database locks and isolation levels.readOnly=true for SELECT-only operations.The Phantom Inventory: How REPEATABLE READ Caused a $50k Stock Discrepancy
SELECT ... FOR UPDATE on the product range.- REPEATABLE READ stops row updates from changing your view, but it does not stop new rows from appearing.
- For any report that aggregates across a dynamic range of rows, use SERIALIZABLE or lock the expected key range.
- Always verify which anomalies your isolation level actually prevents — don't assume it's all three.
SHOW ENGINE INNODB STATUS (MySQL) or SELECT * FROM pg_locks WHERE NOT granted (PostgreSQL) to find the blocking transaction.SELECT * FROM pg_stat_activity WHERE state = 'idle in transaction' or SHOW PROCESSLIST (MySQL). Kill idle transactions over a threshold.FOR UPDATE with condition). Verify by testing with concurrent inserts.Key takeaways
Common mistakes to avoid
3 patternsUsing READ UNCOMMITTED without understanding the consequences
Not handling serialization failures when using SERIALIZABLE isolation
Assuming REPEATABLE READ prevents all read phenomena
SELECT ... WHERE ... FOR UPDATE) to the reading transaction, or switch to SERIALIZABLE for range-reporting transactions.Interview Questions on This Topic
Explain the 'Phantom Read' anomaly. In which isolation level is it resolved in standard SQL, and how does MySQL's InnoDB handle it differently?
Frequently Asked Questions
That's DBMS. Mark it forged?
3 min read · try the examples if you haven't