Database Management Systems
Unit 6: Transaction Processing
ACID Properties, Concurrency Control, Locking Protocols, Deadlocks, Isolation Levels & Recovery — how databases guarantee data correctness when everything goes wrong.
🏢 Oracle & PostgreSQL | 📝 15 MCQs (Bloom's) | 🔬 5 Lab Exercises | 💼 Interview Prep
Why This Chapter Pays Your Salary
Every ₹1 that moves through India's banking system — UPI transfers, ATM withdrawals, online payments — relies on transactions. A transaction is a promise: either ALL operations succeed, or NONE do. Without this guarantee, ₹10,000 could vanish from your account without reaching the recipient. Transaction processing is the foundation of database reliability, and it's the most tested DBMS topic in GATE CS (5-8 marks every year).
🏢 Industry Snapshot
PhonePe/Google Pay — Every UPI transaction is an ACID transaction: (1) Debit sender's bank, (2) Credit receiver's bank, (3) Log to NPCI, (4) Send confirmation. If step 3 fails, steps 1 and 2 are automatically rolled back. PhonePe processes 600 crore transactions/month — every one of them ACID-compliant.
SBI Core Banking — When 50,000 ATMs simultaneously process withdrawals, the database uses Two-Phase Locking (2PL) to ensure no two transactions corrupt the same account balance. If two ATMs try to debit the same account simultaneously, one is blocked until the other commits.
Razorpay — Payment gateway uses SERIALIZABLE isolation for critical payment reconciliation. Lower isolation levels (READ COMMITTED) for dashboard queries — a deliberate performance-vs-correctness trade-off.
Learning Outcomes — Bloom's Taxonomy
| Bloom's Level | Outcome Statement |
|---|---|
| L1 — Remember | Define ACID properties; list transaction states; recall lock types (Shared/Exclusive); name the four SQL isolation levels |
| L2 — Understand | Explain how dirty reads, non-repeatable reads, and phantom reads occur; describe why 2PL guarantees serializability; explain WAL (Write-Ahead Logging) for recovery |
| L3 — Apply | Write SQL transactions with COMMIT, ROLLBACK, SAVEPOINT; construct precedence graphs and test for conflict serializability; apply 2PL to a schedule |
| L4 — Analyze | Analyze a concurrent schedule for conflicts; detect deadlocks using wait-for graphs; determine the isolation level that prevents a given anomaly |
| L5 — Evaluate | Evaluate trade-offs between isolation levels (performance vs correctness); justify when to use optimistic vs pessimistic concurrency control |
| L6 — Create | Design a transaction processing strategy for a payment system with proper locking, isolation levels, error recovery, and deadlock handling |
Concept Explanations
3.1 ACID Properties — The Four Guarantees
📌 ACID — The Contract Every Database Must Honour
A bank transfer of ₹10,000 from Rahul to Priya:
| Property | Meaning | Bank Transfer Example | Guaranteed By |
|---|---|---|---|
| Atomicity | All or nothing — either ALL operations complete, or NONE do | If debiting Rahul succeeds but crediting Priya fails → both are rolled back. Rahul's ₹10,000 is restored. No money vanishes. | Transaction Manager (COMMIT/ROLLBACK) |
| Consistency | Database moves from one valid state to another. All constraints are satisfied before and after. | Total money in the system before = after. If Rahul had ₹50,000 and Priya ₹30,000, after transfer: Rahul=₹40,000, Priya=₹40,000. Total=₹80,000 (unchanged). | Integrity Constraints + Application Logic |
| Isolation | Concurrent transactions don't interfere with each other — each appears to run alone. | If Priya checks her balance WHILE the transfer is in progress, she sees either ₹30,000 (before) or ₹40,000 (after) — never an intermediate state. | Concurrency Control (Locks, MVCC) |
| Durability | Once committed, data survives any failure — power outage, crash, disk error. | After COMMIT, even if the server crashes 1ms later, the transfer is permanently recorded. On restart, the recovery system replays the committed transaction from the log. | Write-Ahead Logging (WAL) + Recovery Manager |
SQL — Transaction in Action
-- ═══════════════════════════════════════
-- Bank Transfer: ₹10,000 from Rahul (A/C 1001) to Priya (A/C 1002)
-- ═══════════════════════════════════════
-- Oracle: transactions start implicitly with the first DML
-- PostgreSQL: explicit BEGIN required
BEGIN; -- PostgreSQL; Oracle: not needed (auto-begin)
SAVEPOINT before_transfer; -- Bookmark in case partial rollback needed
-- Step 1: Debit Rahul
UPDATE accounts SET balance = balance - 10000
WHERE account_id = 1001 AND balance >= 10000; -- Balance check in WHERE
-- Step 2: Verify debit succeeded (row was actually updated)
-- In PL/SQL: IF SQL%ROWCOUNT = 0 THEN ROLLBACK; END IF;
-- Step 3: Credit Priya
UPDATE accounts SET balance = balance + 10000
WHERE account_id = 1002;
-- Step 4: Log the transaction
INSERT INTO transactions (from_acc, to_acc, amount, txn_date, status)
VALUES (1001, 1002, 10000, CURRENT_TIMESTAMP, 'SUCCESS');
COMMIT; -- All 3 operations are now permanent (DURABLE)
-- If ANY step fails:
-- ROLLBACK TO before_transfer; -- Undo to savepoint
-- OR
-- ROLLBACK; -- Undo everything since BEGIN
3.2 Transaction States
Transaction State Diagram
┌─────────────┐
│ ACTIVE │ ← Transaction executing operations
└──────┬──────┘
│
┌────────────┼────────────┐
│ (success) │ (failure)
▼ ▼
┌────────────────┐ ┌────────────────┐
│ PARTIALLY │ │ FAILED │ ← Error occurred
│ COMMITTED │ └───────┬────────┘
└───────┬────────┘ │
│ │ ROLLBACK
│ COMMIT written ▼
│ to disk ┌────────────────┐
▼ │ ABORTED │ ← All changes undone
┌────────────────┐ └────────────────┘
│ COMMITTED │ │
└────────────────┘ │ (restart?)
▼
Re-execute or
terminate
| State | Description | Can Transition To |
|---|---|---|
| Active | Transaction is executing read/write operations | Partially Committed or Failed |
| Partially Committed | Last operation executed; COMMIT issued but not yet flushed to disk | Committed or Failed |
| Committed | All changes are permanently written to disk. Cannot be undone. | Terminal state |
| Failed | An error occurred (constraint violation, system crash, deadlock) | Aborted |
| Aborted | All changes rolled back. Database restored to pre-transaction state. | Restart or terminate |
3.3 Schedules & Serializability
📌 Schedules — How Concurrent Transactions Interleave
A schedule is a sequence of read/write operations from multiple concurrent transactions. A serial schedule runs transactions one after another (no interleaving). A serializable schedule is a concurrent schedule that produces the same result as SOME serial schedule — the gold standard for correctness.
Schedules — Example
T1: Transfer ₹100 from A to B T2: Add 10% interest to A and B
R(A), A=A-100, W(A), R(A), A=A*1.1, W(A),
R(B), B=B+100, W(B) R(B), B=B*1.1, W(B)
Initial: A=1000, B=500
Serial Schedule S1 (T1 then T2):
T1: A=1000-100=900, B=500+100=600
T2: A=900*1.1=990, B=600*1.1=660
Final: A=990, B=660
Serial Schedule S2 (T2 then T1):
T2: A=1000*1.1=1100, B=500*1.1=550
T1: A=1100-100=1000, B=550+100=650
Final: A=1000, B=650
Both are correct! Any concurrent schedule equivalent to S1 or S2 is SERIALIZABLE.
Conflict Serializability — Precedence Graph Test
Conflict Serializability Test
Two operations CONFLICT if: (1) they belong to DIFFERENT transactions,
(2) they access the SAME data item, (3) at least one is a WRITE.
Conflict pairs: R-W, W-R, W-W (NOT R-R — both reads, no conflict)
Test: Build a PRECEDENCE GRAPH (directed graph)
- Node for each transaction
- Edge Ti → Tj if Ti has a conflicting operation BEFORE Tj's conflicting operation
- If graph has NO CYCLE → schedule is CONFLICT SERIALIZABLE
- If graph has a CYCLE → NOT conflict serializable
Example Schedule:
T1: R(A) W(A) R(B) W(B)
T2: R(A) W(A) R(B) W(B)
Conflicts:
T1.W(A) before T2.R(A) → T1 → T2
T1.W(A) before T2.W(A) → T1 → T2
T1.R(B) before T2.W(B) → T1 → T2
T1.W(B) before T2.W(B) → T1 → T2
Precedence Graph: T1 → T2 (all edges same direction)
No cycle! ✓ CONFLICT SERIALIZABLE (equivalent to serial T1,T2)
3.4 Concurrency Problems — What Goes Wrong
| Problem | Description | Example | Prevented By |
|---|---|---|---|
| Dirty Read | T2 reads data WRITTEN by T1 that hasn't committed yet. If T1 rolls back, T2 has read "ghost" data. | T1 updates salary to ₹1,00,000 (uncommitted). T2 reads ₹1,00,000 for a report. T1 rolls back. T2's report is wrong — salary was never ₹1,00,000. | READ COMMITTED and above |
| Non-Repeatable Read | T1 reads a row, T2 updates it and commits, T1 re-reads and gets a DIFFERENT value. | T1 reads balance=₹50,000. T2 debits ₹20,000 and commits. T1 reads again: balance=₹30,000. Two reads in the same transaction return different values. | REPEATABLE READ and above |
| Phantom Read | T1 reads a SET of rows (query), T2 inserts/deletes rows that match, T1 re-runs the same query and gets different ROWS. | T1: SELECT COUNT(*) WHERE dept='CSE' → 50. T2: INSERT student in CSE, COMMIT. T1: same query → 51. A "phantom" row appeared. | SERIALIZABLE only |
| Lost Update | T1 and T2 both read the same value, both update it — one update is lost. | Balance=₹10,000. T1 reads ₹10,000, deducts ₹3,000. T2 reads ₹10,000, deducts ₹5,000. T1 writes ₹7,000. T2 writes ₹5,000. T1's deduction is LOST. Correct: ₹2,000. | Proper locking (any level) |
3.5 Lock-Based Concurrency Control
Lock Types
┌─────────────────────────────────────────────────────┐
│ Lock Type │ Symbol │ Allows Concurrent │
├─────────────────┼────────┼──────────────────────────│
│ Shared (S) │ S │ Other S locks: YES ✓ │
│ (Read Lock) │ │ Other X locks: NO ✗ │
├─────────────────┼────────┼──────────────────────────│
│ Exclusive (X) │ X │ Other S locks: NO ✗ │
│ (Write Lock) │ │ Other X locks: NO ✗ │
└─────────────────┴────────┴──────────────────────────┘
Lock Compatibility Matrix:
│ Requesting S │ Requesting X │
─────────┼──────────────┼──────────────│
Held: S │ GRANTED ✓ │ WAIT ✗ │
Held: X │ WAIT ✗ │ WAIT ✗ │
None │ GRANTED ✓ │ GRANTED ✓ │
Rule: Multiple readers OK. Any writer = exclusive access.
Two-Phase Locking (2PL)
📌 2PL — The Protocol That Guarantees Serializability
A transaction must acquire all its locks before releasing any. Two phases: Growing phase (acquire locks, never release) → Shrinking phase (release locks, never acquire). Once a lock is released, no new locks can be acquired.
⚙️ EXAMPLE2PL Example
T1: Lock-S(A), Read(A), Lock-X(B), Write(B) | Unlock(A), Unlock(B)
← ─ ─ Growing Phase ─ ─ ─ ─ ─ ─ ─ ─ → ← Shrinking Phase →
VALID 2PL: all locks acquired before any release ✓
T2: Lock-S(A), Read(A), Unlock(A), Lock-X(B), Write(B), Unlock(B)
← ─ Growing ─ → ← ─ ─ ─ NOT 2PL! ─ ─ →
Released A then acquired B → VIOLATES 2PL ✗
📌 VARIANTS
| Variant | Rule | Deadlock? | Cascading Abort? |
|---|---|---|---|
| Basic 2PL | Grow then shrink | Yes ⚠️ | Yes ⚠️ |
| Strict 2PL | Hold ALL exclusive (X) locks until COMMIT/ABORT | Yes ⚠️ | No ✓ |
| Rigorous 2PL | Hold ALL locks (S and X) until COMMIT/ABORT | Yes ⚠️ | No ✓ |
Industry standard: Most databases (Oracle, PostgreSQL) use Strict 2PL — exclusive locks held until COMMIT. This prevents cascading aborts (where one rollback forces other transactions to rollback too).
3.6 Deadlocks — The Deadly Embrace
Deadlock Scenario
T1: Lock-X(A) ───────────► Lock-X(B) ← WAITING for T2 to release B
T2: Lock-X(B) ───────────► Lock-X(A) ← WAITING for T1 to release A
T1 holds A, wants B.
T2 holds B, wants A.
Neither can proceed. DEADLOCK! ☠️
Wait-For Graph:
T1 ──► T2 ──► T1 (CYCLE detected → deadlock exists)
Resolution: The DBMS detects the cycle and ABORTS one transaction
(the "victim" — usually the one with least work done).
The victim is rolled back, its locks released, and the other proceeds.
Deadlock Handling Strategies
| Strategy | How It Works | Used By |
|---|---|---|
| Detection | Periodically check wait-for graph for cycles. If found, abort one transaction (victim selection). Most common in practice. | Oracle, PostgreSQL (every few seconds) |
| Prevention (Wait-Die) | Older transaction waits; younger transaction is aborted ("dies") and restarted. Based on timestamps. | Academic / specialized systems |
| Prevention (Wound-Wait) | Older transaction "wounds" (forces rollback of) younger transaction. Younger waits for older. | Google Spanner |
| Timeout | If a transaction waits longer than N seconds for a lock, assume deadlock and abort it. | MySQL InnoDB (innodb_lock_wait_timeout) |
3.7 SQL Isolation Levels
SQL — Setting Isolation Levels
-- Oracle: default is READ COMMITTED
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- PostgreSQL: same syntax, default is READ COMMITTED
BEGIN;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- ... your queries ...
COMMIT;
-- Session-level (all transactions in this session):
ALTER SESSION SET ISOLATION_LEVEL = SERIALIZABLE; -- Oracle
SET default_transaction_isolation = 'serializable'; -- PostgreSQL
| Isolation Level | Dirty Read | Non-Repeatable Read | Phantom Read | Performance |
|---|---|---|---|---|
| READ UNCOMMITTED | ⚠️ Possible | ⚠️ Possible | ⚠️ Possible | 🟢 Fastest |
| READ COMMITTED | ✅ Prevented | ⚠️ Possible | ⚠️ Possible | 🟢 Fast (default) |
| REPEATABLE READ | ✅ Prevented | ✅ Prevented | ⚠️ Possible | 🟡 Medium |
| SERIALIZABLE | ✅ Prevented | ✅ Prevented | ✅ Prevented | 🔴 Slowest |
• Oracle: Default = READ COMMITTED. Oracle uses MVCC (Multi-Version Concurrency Control) — readers never block writers, writers never block readers. Each transaction sees a consistent snapshot. Oracle doesn't implement READ UNCOMMITTED or REPEATABLE READ — only READ COMMITTED and SERIALIZABLE.
• PostgreSQL: Default = READ COMMITTED. Supports all four levels. REPEATABLE READ in PG actually provides snapshot isolation (prevents phantoms too in practice).
• Rule of thumb: Use READ COMMITTED for 95% of operations. Use SERIALIZABLE only for critical financial transactions (payment reconciliation, ledger posting).
3.8 Recovery Systems — Surviving Crashes
📌 Write-Ahead Logging (WAL) — The Safety Net
Before any data page is written to disk, the corresponding log record MUST be written to the log file first. This ensures that even if the system crashes mid-write, the recovery manager can reconstruct the database state from the log.
⚙️ HOW IT WORKSWAL Example — Bank Transfer
Log (written FIRST): Data (written AFTER log):
LSN 1: [T1, BEGIN]
LSN 2: [T1, UPDATE, A, old=1000, new=900]
LSN 3: [T1, UPDATE, B, old=500, new=600]
LSN 4: [T1, COMMIT] ← Log flushed to disk HERE
Now data pages can be written:
Page(A): 1000 → 900
Page(B): 500 → 600
CRASH after LSN 4 but before data pages written?
Recovery: Read log → REDO T1's changes (A=900, B=600)
Data is restored correctly! ✓
CRASH after LSN 3 but before COMMIT (LSN 4)?
Recovery: T1 not committed → UNDO T1's changes
A=1000 (restored), B=500 (restored)
Transaction is rolled back. ✓
ARIES Recovery Algorithm
ARIES — Three Phases of Recovery
After a crash, the recovery manager runs three phases:
Phase 1: ANALYSIS
Scan log from last checkpoint forward.
Determine: which transactions were active at crash time.
Build: dirty page table (pages that may not be on disk).
Phase 2: REDO (Repeating History)
Replay ALL logged operations (committed and uncommitted).
Bring database to exactly the state at crash time.
Uses LSN (Log Sequence Number) to avoid re-doing already-applied changes.
Phase 3: UNDO (Rolling Back Losers)
For transactions that were active (not committed) at crash time:
Undo their operations in REVERSE order.
Write compensation log records (CLR) to prevent re-undoing on another crash.
Result: Committed transactions → REDONE (durable).
Uncommitted transactions → UNDONE (atomic).
Database is consistent. ✓
Checkpoints
Checkpoint — Reducing Recovery Time
Without checkpoints: must scan ENTIRE log from the beginning. Slow!
Checkpoint: periodically flush all dirty pages to disk and record
which transactions are active.
Recovery starts from the LAST CHECKPOINT, not the beginning of the log.
Oracle: Automatic checkpoint every 3 seconds (or configurable).
PostgreSQL: checkpoint_timeout (default 5 minutes).
Fuzzy Checkpoint: allows transactions to continue during checkpointing
(non-blocking). Used by all modern databases.
Industry Problems
🏢 Industry Problem #1 — PhonePe UPI Double-Spend Prevention
Scenario: A user initiates a ₹5,000 UPI payment. Due to network latency, the app retries the request. The backend receives TWO identical requests 200ms apart. How do you prevent the user from being charged twice?
💡 Solution: Idempotency + Serializable Transaction
SQL — Idempotent Transaction
-- Each UPI request has a unique txn_ref_id generated by the app
-- The database uses a UNIQUE constraint to prevent double processing
CREATE TABLE upi_transactions (
txn_ref_id VARCHAR2(50) PRIMARY KEY, -- App-generated unique ID
sender_vpa VARCHAR2(50) NOT NULL,
receiver_vpa VARCHAR2(50) NOT NULL,
amount NUMBER(12,2),
status VARCHAR2(10),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Transaction procedure (idempotent)
CREATE OR REPLACE PROCEDURE sp_upi_pay (
p_ref_id IN VARCHAR2,
p_sender IN VARCHAR2,
p_receiver IN VARCHAR2,
p_amount IN NUMBER,
p_result OUT VARCHAR2
) AS
v_existing NUMBER;
BEGIN
-- Check if this exact request was already processed (idempotency)
SELECT COUNT(*) INTO v_existing
FROM upi_transactions WHERE txn_ref_id = p_ref_id;
IF v_existing > 0 THEN
p_result := 'DUPLICATE — already processed';
RETURN; -- Second request safely ignored
END IF;
-- Process the payment (SERIALIZABLE to prevent race condition)
UPDATE accounts SET balance = balance - p_amount
WHERE vpa = p_sender AND balance >= p_amount;
IF SQL%ROWCOUNT = 0 THEN
p_result := 'FAILED: Insufficient balance';
ROLLBACK; RETURN;
END IF;
UPDATE accounts SET balance = balance + p_amount
WHERE vpa = p_receiver;
INSERT INTO upi_transactions VALUES
(p_ref_id, p_sender, p_receiver, p_amount, 'SUCCESS', SYSTIMESTAMP);
COMMIT;
p_result := 'SUCCESS';
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN -- Race condition: both requests arrive simultaneously
ROLLBACK;
p_result := 'DUPLICATE — concurrent retry caught';
WHEN OTHERS THEN
ROLLBACK;
p_result := 'FAILED: ' || SQLERRM;
END;
/
Key insight: The UNIQUE constraint on txn_ref_id is the final safety net. Even if the SELECT check passes for both concurrent requests, only one INSERT will succeed — the other hits DUP_VAL_ON_INDEX and is rolled back.
🏢 Industry Problem #2 — GATE CS: Precedence Graph & Serializability
Problem: Given schedule S: R1(A), R2(A), W1(A), W2(B), R1(B), W1(B). Is S conflict serializable?
💡 Complete Solution
Solution
Step 1: List all conflicting pairs (different Tx, same item, at least one W)
R1(A) vs W2(A)? W2(A) doesn't exist. Skip.
R2(A) vs W1(A): R2 before W1 → T2 → T1
W1(A) vs R2(A)? W1 is AFTER R2 → already counted above.
W2(B) vs R1(B): W2 before R1 → T2 → T1
W2(B) vs W1(B): W2 before W1 → T2 → T1
R1(B) vs W2(B)? R1 is AFTER W2 → already counted.
Step 2: Build precedence graph
T2 → T1 (from R2(A) vs W1(A))
T2 → T1 (from W2(B) vs R1(B))
T2 → T1 (from W2(B) vs W1(B))
All edges: T2 → T1. No reverse edge.
Step 3: Check for cycle
T2 → T1 only. NO CYCLE! ✓
Step 4: Conclusion
S is CONFLICT SERIALIZABLE.
Equivalent serial order: T2, T1 (T2 before T1 in the serial schedule).
🏢 Industry Problem #3 — Razorpay Deadlock in Payment Settlement
Scenario: Two settlement processes run concurrently. Process A: Lock merchant_1 account, then lock merchant_2 account. Process B: Lock merchant_2 account, then lock merchant_1 account. Deadlock!
💡 Solution: Consistent Lock Ordering
SQL — Deadlock Prevention
-- WRONG: Different lock ordering → deadlock possible
-- Process A: UPDATE merchant_1 ... UPDATE merchant_2
-- Process B: UPDATE merchant_2 ... UPDATE merchant_1
-- CORRECT: Always lock in the SAME order (by account_id ascending)
CREATE OR REPLACE PROCEDURE sp_settle (p_acc1 NUMBER, p_acc2 NUMBER) AS
v_first NUMBER := LEAST(p_acc1, p_acc2);
v_second NUMBER := GREATEST(p_acc1, p_acc2);
BEGIN
-- Always lock smaller account_id FIRST
SELECT balance FROM accounts WHERE account_id = v_first FOR UPDATE;
SELECT balance FROM accounts WHERE account_id = v_second FOR UPDATE;
-- Now both are locked in consistent order → no deadlock possible
UPDATE accounts SET balance = balance - 1000 WHERE account_id = p_acc1;
UPDATE accounts SET balance = balance + 1000 WHERE account_id = p_acc2;
COMMIT;
END;
/
-- Both Process A and B call sp_settle → both lock merchant_1 first
-- One waits, the other completes and releases → NO DEADLOCK ✓
DBA Insight: Razorpay's payment engine enforces consistent lock ordering across all settlement procedures. Their DBA team monitors V$LOCK (Oracle) or pg_locks (PostgreSQL) for lock contention, and has alerts for deadlock frequency exceeding 0.01% of transactions.
Lab Exercises
Exercise 1: ACID in Action — COMMIT, ROLLBACK, SAVEPOINT
Schema: Use the e-commerce schema (orders, customers, products, order_items)
Tasks:
- Insert a new order with 3 items. COMMIT. Verify data persists.
- Insert another order, then ROLLBACK. Verify data is gone.
- Insert an order, SAVEPOINT sp1, insert items, ROLLBACK TO sp1. Verify: order exists but items don't.
- Simulate Atomicity: Write a PL/SQL block that transfers ₹5,000 between accounts. If debit succeeds but credit fails (account doesn't exist), ROLLBACK the entire transaction.
- Test Durability: COMMIT a transaction, then close SQL*Plus. Reconnect and verify data persists.
Exercise 2: Observe Concurrency Problems
Setup: Open TWO SQL*Plus sessions (Session A and Session B) connected to the same database.
Tasks:
- Dirty Read test: Session A: UPDATE product price (don't COMMIT). Session B: SELECT that product. Does B see the uncommitted change? (Answer: No in Oracle — READ COMMITTED default prevents it.)
- Non-Repeatable Read: Session A: SELECT price. Session B: UPDATE price, COMMIT. Session A: SELECT again. Same value? (Answer: Different — non-repeatable read occurred.)
- Lost Update: Both sessions: SELECT balance, compute new value, UPDATE. Verify one update is lost.
- FOR UPDATE lock: Repeat lost update test using SELECT...FOR UPDATE. Verify it's prevented.
- Set Session A to SERIALIZABLE. Repeat non-repeatable read test. Verify it's prevented.
Exercise 3: Precedence Graphs & Serializability
Tasks (pen-and-paper + verify):
- Schedule: R1(A), W2(A), R2(B), W1(B). Draw precedence graph. Is it conflict serializable?
- Schedule: R1(A), R2(B), W1(B), W2(A). Draw precedence graph. Check for cycles.
- Schedule: R1(A), R2(A), W2(A), R1(A), W1(A). Build graph. Serializable?
- For each serializable schedule, state the equivalent serial order.
- Create a schedule with 3 transactions that has a cycle in the precedence graph → NOT serializable.
Exercise 4: Deadlock Detection & Prevention
Tasks:
- Create a deadlock: Session A:
UPDATE accounts SET balance=balance-100 WHERE id=1(don't commit). Session B:UPDATE accounts SET balance=balance-100 WHERE id=2(don't commit). Session A:UPDATE ... WHERE id=2(waits). Session B:UPDATE ... WHERE id=1(deadlock!). - Observe Oracle's deadlock detection message (ORA-00060). Which session was the victim?
- Fix the deadlock by implementing consistent lock ordering (LEAST/GREATEST pattern).
- Draw the wait-for graph for the deadlock scenario.
- Query
V$LOCK(Oracle) orpg_locks(PostgreSQL) to see active locks in real time.
Exercise 5: Complete Payment Transaction System
Tasks: Design and implement a complete payment processing system:
- Create tables: accounts, transactions, audit_log
- Procedure
sp_payment: transfer with balance check, idempotency (txn_ref_id), audit logging - Trigger: log every balance change to audit_log with old/new values
- Test: Run 5 transfers, including one with insufficient balance and one duplicate request
- Simulate crash recovery: INSERT without COMMIT, disconnect, reconnect — verify uncommitted data is gone (Atomicity). INSERT with COMMIT, disconnect, reconnect — verify data persists (Durability).
MCQ Assessment Bank — 15 Questions
Hover to reveal answer and explanation.
Which ACID property guarantees that a committed transaction survives a system crash?
- Atomicity
- Consistency
- Isolation
- Durability
In lock-based concurrency, a Shared (S) lock allows:
- Other transactions to acquire both S and X locks
- Other transactions to acquire S locks but NOT X locks on the same data item
- No other locks at all
- Only the same transaction to read
What are the four SQL isolation levels, from weakest to strongest?
- READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE
- SERIALIZABLE, REPEATABLE READ, READ COMMITTED, READ UNCOMMITTED
- READ COMMITTED, SERIALIZABLE, REPEATABLE READ, READ UNCOMMITTED
- There are only two levels
What is a dirty read and why is it dangerous?
- Reading data from a corrupted disk
- Transaction T2 reads a value written by T1 that hasn't committed yet. If T1 later rolls back, T2 has used data that never officially existed — leading to incorrect results, wrong reports, or cascading errors in dependent transactions
- Reading the same row twice
- Reading from a locked table
Why does Two-Phase Locking (2PL) guarantee conflict serializability?
- It doesn't — 2PL only prevents deadlocks
- Because 2PL divides each transaction into a growing phase (only acquire locks) and shrinking phase (only release locks). This ordering constraint ensures that conflicting operations between transactions are ordered consistently, making the resulting schedule equivalent to some serial schedule
- 2PL locks the entire database
- 2PL uses timestamps instead of locks
What is MVCC and how does it improve performance over traditional locking?
- MVCC and locking are the same
- MVCC maintains multiple versions of each row. Readers see a consistent snapshot without acquiring locks. Writers create new versions. Result: readers never block writers and writers never block readers — eliminating the biggest performance bottleneck in lock-based systems
- MVCC removes all locks
- MVCC only works with NoSQL
Given: R1(A), R2(A), W1(A), R1(B), R2(B), W2(B). Build the precedence graph and determine if it's conflict serializable.
- Not serializable — cycle exists
- Conflicts: R2(A) before W1(A) → T2→T1. R1(B) before W2(B) → T1→T2. Graph: T2→T1 and T1→T2. CYCLE exists → NOT conflict serializable
- Serializable — equivalent to T1,T2
- Cannot determine without more information
Write SQL to set up a SERIALIZABLE transaction that reads account balance, deducts ₹5,000, and commits.
- Just use UPDATE directly
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; UPDATE accounts SET balance = balance - 5000 WHERE account_id = 1001 AND balance >= 5000; COMMIT;— SERIALIZABLE prevents phantoms and non-repeatable reads, ensuring no other transaction can modify the balance between the check and the update- SERIALIZABLE is not a valid SQL keyword
- Use READ UNCOMMITTED for best performance
balance >= 5000 prevents overdraft. At SERIALIZABLE level, if another transaction modifies the same account concurrently, one of them will receive a serialization failure error and must retry. This is the strongest guarantee — used for critical financial operations.🏢 Razorpay uses SERIALIZABLE for payment ledger postings but READ COMMITTED for dashboard queries.
Apply Strict 2PL to: T1 reads A, writes A; T2 reads A, writes A. Show the lock/unlock sequence.
- No locking needed
- T1: Lock-S(A), R(A), Lock-X(A)↑, W(A), COMMIT→Unlock-X(A). T2: Lock-S(A)→WAIT until T1 commits. After T1 commits: T2 gets Lock-S(A), R(A), Lock-X(A)↑, W(A), COMMIT→Unlock-X(A). Strict 2PL: X-locks held until COMMIT. T2 must wait for T1's commit — no dirty reads, no lost updates
- Both can lock simultaneously
- Locks are released immediately after each operation
Given three transactions with the following wait-for relationships: T1 waits for T2, T2 waits for T3, T3 waits for T1. Analyze for deadlock.
- No deadlock — all transactions will eventually complete
- DEADLOCK exists. Wait-for graph: T1→T2→T3→T1 forms a cycle. No transaction can proceed. Resolution: abort one transaction (victim). Choose T3 (e.g., least work done). T3 aborted → T2 proceeds → T2 commits → T1 proceeds → T1 commits. Restart T3.
- Only two transactions can deadlock
- Wait-for graphs can't have 3 nodes
A schedule shows: T1 reads balance=₹10,000, T2 reads balance=₹10,000, T1 writes balance=₹7,000, T2 writes balance=₹5,000. What concurrency problem occurred? What's the final balance?
- No problem — final balance is correct
- LOST UPDATE. T1's deduction (₹3,000) is overwritten by T2's deduction (₹5,000). Final balance: ₹5,000 (T2's write). Correct balance should be: ₹10,000 - ₹3,000 - ₹5,000 = ₹2,000. The bank LOST ₹3,000. Fix: use SELECT...FOR UPDATE to lock the row before reading, or use SERIALIZABLE isolation
- Dirty read
- Phantom read
A startup uses READ UNCOMMITTED for all transactions "because it's fastest." Their financial reports occasionally show incorrect totals. Evaluate this decision.
- READ UNCOMMITTED is fine for all workloads
- Critical mistake. READ UNCOMMITTED allows dirty reads — reports can include uncommitted data that may be rolled back, producing incorrect totals. For financial data, at minimum READ COMMITTED is required (Oracle/PG default). For reconciliation/audit reports, SERIALIZABLE should be used. READ UNCOMMITTED is acceptable ONLY for approximate counts or non-critical monitoring dashboards where slight inaccuracy is tolerable
- Use SERIALIZABLE for everything
- Isolation levels don't affect correctness
A DBA team debates: pessimistic locking (SELECT...FOR UPDATE) vs optimistic concurrency control (version column check). Evaluate both for a high-traffic e-commerce inventory system.
- Pessimistic is always better
- Pessimistic (FOR UPDATE): acquires lock on read — guarantees no conflict but reduces concurrency (other transactions wait). Best for HIGH-contention scenarios (few products with heavy demand, e.g., flash sale). Optimistic (version check): read without lock, check version on update — retry if version changed. Best for LOW-contention scenarios (many products, few conflicts). For Flipkart's flash sale (1000 users buying the same item): pessimistic. For regular catalog browsing: optimistic
- Optimistic is always better
- Neither works for databases
Design a crash recovery strategy for a hospital database. Explain what happens during: (1) system crash during an INSERT, (2) disk failure, (3) application error during a multi-step procedure.
- Just restart the server
- (1) System crash during INSERT: WAL recovery — if COMMIT was logged, REDO the INSERT on restart. If not, UNDO the partial INSERT (atomicity preserved). (2) Disk failure: restore from last backup + apply WAL/archive logs for point-in-time recovery (PITR). Oracle: RMAN backup + archived redo logs. PostgreSQL: pg_basebackup + WAL archive. (3) Application error: PL/SQL EXCEPTION handler catches the error, ROLLBACK undoes all changes, logs the error, returns status to caller. SAVEPOINT allows partial rollback of multi-step procedures
- Backups are sufficient
- Recovery is automatic and needs no planning
Design a complete transaction processing strategy for a UPI payment system handling 10,000 transactions per second.
- Use file-based storage
- Architecture: (1) Idempotent transactions with unique txn_ref_id (prevents double-spend on retries). (2) Strict 2PL with SELECT...FOR UPDATE for account locking. (3) READ COMMITTED for routine transactions, SERIALIZABLE for end-of-day reconciliation. (4) Consistent lock ordering (by account_id) to prevent deadlocks. (5) WAL with synchronous replication to standby for durability. (6) Connection pooling + partitioned tables for 10K TPS throughput. (7) ARIES recovery for crash resilience. (8) Circuit breaker pattern for downstream bank failures
- NoSQL is required for this scale
- Single-threaded processing
Chapter Summary
🎯 3 Skills This Chapter Unlocks
- Transaction Design — You can design ACID-compliant transactions for banking, payments, and e-commerce. Every enterprise developer must know COMMIT, ROLLBACK, SAVEPOINT, and FOR UPDATE.
- Concurrency Reasoning — You can analyze schedules for serializability, detect deadlocks, and choose appropriate isolation levels. This is a core GATE CS topic (5-8 marks) and a senior DBA interview essential.
- Recovery Architecture — You understand how databases survive crashes. WAL, checkpoints, and ARIES are the foundation of database reliability that banks, hospitals, and governments depend on daily.
📋 Transaction Processing Quick Reference
ACID: Atomicity (all/nothing), Consistency (valid states),
Isolation (no interference), Durability (survives crash)
TRANSACTION: BEGIN → DML → COMMIT or ROLLBACK
SAVEPOINT sp1; ... ROLLBACK TO sp1;
SERIALIZABILITY TEST:
1. Find conflict pairs (diff Tx, same item, ≥1 write)
2. Draw precedence graph (edges: earlier conflict → later)
3. No cycle = serializable ✓ | Cycle = NOT serializable ✗
LOCKS: S (read) — multiple OK | X (write) — exclusive only
SELECT ... FOR UPDATE; -- Acquires X-lock on selected rows
2PL: Growing (acquire) → Shrinking (release)
Strict 2PL: X-locks until COMMIT (industry standard)
DEADLOCK: Cycle in wait-for graph → abort victim
Prevention: lock resources in consistent order (LEAST/GREATEST)
ISOLATION LEVELS:
READ UNCOMMITTED < READ COMMITTED < REPEATABLE READ < SERIALIZABLE
Default: READ COMMITTED (Oracle, PostgreSQL)
RECOVERY: WAL (Write-Ahead Log) + ARIES (Analysis → Redo → Undo)
Checkpoint: limits recovery scan | PITR: backup + WAL replay
Interview & Career Preparation
Q1: Explain ACID properties with a real-world example.
Model Answer: Bank transfer of ₹10,000: Atomicity — if debit succeeds but credit fails, both are rolled back (money doesn't vanish). Consistency — total money before = after (₹80,000 stays ₹80,000). Isolation — if someone checks the balance mid-transfer, they see either before or after, never an intermediate state. Durability — after COMMIT, even if the server crashes, the transfer is permanent (WAL ensures this).
Q2: What is conflict serializability? How do you test it?
Model Answer: A schedule is conflict serializable if it can be transformed into a serial schedule by swapping non-conflicting operations. Test: build a precedence graph — node per transaction, edge Ti→Tj if Ti has a conflicting operation before Tj. If the graph has NO CYCLE, the schedule is conflict serializable. The topological sort of the graph gives the equivalent serial order. Two operations conflict if they access the same data item, belong to different transactions, and at least one is a write.
Q3: What is Two-Phase Locking (2PL)?
Model Answer: 2PL requires each transaction to divide its locking into two phases: Growing phase (only acquire locks, never release) and Shrinking phase (only release locks, never acquire). This guarantees conflict serializability. Strict 2PL (industry standard) holds all exclusive locks until COMMIT — prevents cascading aborts and dirty reads. 2PL does NOT prevent deadlocks — two transactions can still wait for each other's locks.
Q4: What is a deadlock? How do databases handle it?
Model Answer: Deadlock: two or more transactions each hold locks that the other needs, creating a cycle where none can proceed. Detection: DBMS periodically checks the wait-for graph for cycles. If found, one transaction (the victim) is aborted and its locks released. Prevention: consistent lock ordering (always lock resources in the same sequence), timeout-based abortion. Oracle detects deadlocks within seconds and raises ORA-00060.
Q5: Explain the four isolation levels.
Model Answer: READ UNCOMMITTED: allows dirty reads (reading uncommitted data). READ COMMITTED: prevents dirty reads; each query sees only committed data (Oracle/PG default). REPEATABLE READ: prevents non-repeatable reads; same query returns same value within a transaction. SERIALIZABLE: prevents all anomalies including phantoms; transactions appear to run serially. Trade-off: stronger isolation = fewer anomalies but more lock contention and lower throughput.
Q6: What is MVCC? How does it differ from locking?
Model Answer: MVCC (Multi-Version Concurrency Control) maintains multiple versions of each row. Instead of blocking readers with locks, each transaction sees a consistent snapshot of the data as of its start time. Readers don't block writers; writers don't block readers. Oracle uses undo segments to store old row versions. PostgreSQL stores old versions in the table (cleaned by VACUUM). MVCC provides better concurrency than pure locking because read-write contention is eliminated.
Q7: What is Write-Ahead Logging (WAL)?
Model Answer: WAL rule: before any data modification is written to the data file, the corresponding log record must be written to the log file first. This ensures recoverability: if the system crashes after writing the log but before writing data, the recovery manager can REDO the operation from the log. If the crash occurs before the COMMIT log record, the recovery manager UNDOs the incomplete transaction. WAL is the foundation of durability in ACID. Both Oracle (redo log) and PostgreSQL (WAL) use this mechanism.
Q8: How would you prevent a lost update in SQL?
Model Answer: Three approaches: (1) Pessimistic: SELECT balance FROM accounts WHERE id=1 FOR UPDATE — locks the row before reading, preventing other transactions from reading/modifying it until COMMIT. (2) Optimistic: add a version column. Read version, UPDATE with WHERE version = read_version. If 0 rows updated, someone else modified it — retry. (3) Atomic UPDATE: UPDATE accounts SET balance = balance - 5000 WHERE id=1 AND balance >= 5000 — reads and writes in one atomic operation. Approach 1 for high-contention; approach 3 is simplest for single-column updates.
Q9: What is the ARIES recovery algorithm?
Model Answer: ARIES (Algorithm for Recovery and Isolation Exploiting Semantics) has three phases: (1) Analysis: scan log from last checkpoint, identify active transactions and dirty pages at crash time. (2) Redo: replay ALL logged operations to bring database to crash-time state — both committed and uncommitted. (3) Undo: roll back uncommitted transactions in reverse order, writing Compensation Log Records (CLRs) to prevent re-undoing on subsequent crashes. ARIES uses LSN (Log Sequence Numbers) to avoid redundant redo and supports fuzzy checkpoints for better performance.
Q10: How do companies like PhonePe handle 10,000 UPI transactions per second?
Model Answer: Architecture: (1) Horizontal partitioning — accounts partitioned by hash(account_id) across multiple database shards. Each shard handles ~1000 TPS. (2) Connection pooling — pgBouncer/Oracle UCP reuses database connections. (3) READ COMMITTED for most operations (fast); SERIALIZABLE only for reconciliation. (4) Idempotent design — unique txn_ref_id prevents double-charging on retries. (5) Consistent lock ordering prevents deadlocks. (6) Synchronous WAL replication to standby for zero RPO. (7) Circuit breakers for downstream bank failures. (8) Monitoring: lock contention, deadlock frequency, WAL lag.