DBMS 📂 Transactions and Concurrency: · 5 of 6 30 min read

Concurrency Control in DBMS: Locking and Two-Phase Locking (2PL)

A tutorial on lock-based concurrency control covering shared/exclusive locks, the compatibility matrix, Two-Phase Locking (growing phase, lock point, shrinking phase) with a hill diagram, all four 2PL variants (Basic, Strict, Rigorous, Conservative) with timeline SVGs, deadlocks with Wait-For Graphs and Wait-Die/Wound-Wait prevention, solved numericals, and an academic-vs-industry comparison.

Section 01

The Story That Explains Locking

The Library Reserve Room
A rare manuscript sits in a library vault. Scholars may visit — but only under rules. Reading is harmless: multiple scholars can sit side by side and read simultaneously. Nobody changes the text.

Annotating (writing) is dangerous. If two scholars annotate the same page at the same time, their notes will clash. So the librarian has a policy: if anyone wants to annotate a page, they must lock the vault door and work alone. Others — readers or writers — must wait outside until the door is unlocked.

This is exactly how a database manages shared and exclusive locks. Multiple shared locks (reads) can coexist. But an exclusive lock (write) demands sole access.

Now the tricky part: when can a scholar release the lock? If they release too early, another scholar may see a half-finished annotation, producing inconsistency. The rule that governs the timing of lock and unlock is called Two-Phase Locking (2PL).

Concurrency control is the DBMS subsystem responsible for ensuring that transactions executing in parallel do not violate the isolation property. The most widely used mechanism is lock-based concurrency control, where a transaction must obtain a lock on a data item before accessing it, and release the lock when done. Two-Phase Locking (2PL) is the most important protocol governing when locks may be acquired and released — and it is provably sufficient to guarantee conflict serializability.


Section 02

Lock Types — Shared and Exclusive

Every lock request falls into one of two modes. The compatibility between them determines whether concurrency is possible.

🔒 Shared Lock — S-lock (Read Lock)
PropertyDetail
PurposeAllow a transaction to read a data item
SharingMultiple transactions may hold S-locks on the same item simultaneously
SymbolS or lock-S(X)
🔓 Exclusive Lock — X-lock (Write Lock)
PropertyDetail
PurposeAllow a transaction to read AND write a data item
SharingOnly one transaction may hold an X-lock — no sharing at all
SymbolX or lock-X(X)

Lock Compatibility Matrix

This 2×2 matrix is the single most important table in locking. It answers: "Can transaction Tj get its requested lock if Ti already holds a lock on the same item?"

Ti holds S-lockTi holds X-lock
Tj requests S-lock✓ GRANTED✗ WAIT
Tj requests X-lock✗ WAIT✗ WAIT
💡
Memory Hook

"Readers share, writers are exclusive." The only compatible combination is two shared locks. Every other pair (S-X, X-S, X-X) forces the second transaction to wait.


Section 03

Basic Lock Protocol — Lock Before Access, Unlock When Done

The simplest locking rule: acquire a lock before accessing a data item, release it when you no longer need it. But "when you no longer need it" is dangerously vague — releasing too early creates problems.

Example — Early Unlock Breaks Isolation

TimeT1T2Item A
t1lock-X(A)locked by T1
t2R(A) = 100
t3W(A) = 150
t4unlock(A)unlocked early!
t5lock-X(A)locked by T2
t6R(A) = 150
t7W(A) = 200
t8unlock(A)
t9ABORT!A must revert to 100
⚠️
The Cascading Abort

T1 unlocked A at t4 before committing. T2 read the uncommitted value 150 and built its own work on top of it. When T1 aborts at t9, A reverts to 100 — but T2 has already used the stale value 150. T2 must also abort. This is the exact cascading-abort problem from the Schedules tutorial — caused here by releasing the lock too soon.

The fix: a protocol that enforces a disciplined lock lifecycle. That protocol is Two-Phase Locking.


Section 04

Two-Phase Locking (2PL) — The Core Idea

📝
The Formal Definition

A transaction follows the Two-Phase Locking protocol if it divides its lifetime into exactly two phases: a growing phase (only lock acquisitions, no releases) followed by a shrinking phase (only lock releases, no new acquisitions). The transition point is called the lock point.

⚖️ The Two Phases
Phase 1
Growing Phase: the transaction may acquire new locks (S or X) but must not release any lock. The set of held locks only increases.
Lock Point
The instant the transaction acquires its last lock. This is the peak of the "hill". After this moment, no new lock may be requested.
Phase 2
Shrinking Phase: the transaction may release locks but must not acquire any new lock. The set of held locks only decreases, until all are released.

The Lock-Count Diagram

Two-Phase Locking — Lock Count Over Time
Time → # Locks Growing (acquire only) Shrinking (release only) Lock Point

The "hill" shape is the hallmark of 2PL: locks only go up (growing), reach a peak (lock point), then only go down (shrinking). Once you release one lock, you can never acquire another.

🎯
The Guarantee

Theorem: If every transaction in a schedule follows 2PL, then the schedule is conflict serializable. The equivalent serial order is the order of the transactions' lock points. This is the formal bridge from locking to serializability.


Section 05

Variants of Two-Phase Locking

The basic 2PL rule guarantees conflict serializability, but leaves open when exactly locks are released. Different placement of the shrinking phase yields different guarantees about recovery and cascading aborts.

📈
Basic 2PL
Growing then Shrinking
Locks may be released as soon as the growing phase ends. Guarantees: conflict serializability. Risks: cascading aborts and deadlock.
🔒
Strict 2PL (S2PL)
Hold X-locks until commit
All exclusive (write) locks are held until the transaction commits or aborts. Shared locks may be released earlier. Guarantees: conflict serializable + cascadeless.
🔐
Rigorous 2PL (SS2PL)
Hold ALL locks until commit
All locks (S and X) are held until commit/abort. The shrinking phase collapses to a single instant at commit. Guarantees: conflict serializable + strict (which implies cascadeless).
🚀
Conservative 2PL
Lock everything upfront
All locks are acquired before the transaction begins any work. Guarantees: deadlock-free (but limits concurrency and is hard to know all locks in advance).

Visual Comparison — When Are Locks Released?

2PL Variant Timelines
BEGIN COMMIT Basic 2PL Growing Shrinking Strict 2PL Growing X-locks held → commit S-locks shrink Rigorous 2PL Growing ALL locks held → commit Conser- vative All locks held entire lifetime → commit

From top to bottom, each variant holds locks progressively longer. Conservative 2PL acquires everything at BEGIN and holds to COMMIT — deadlock-free but least concurrent.

VariantConflict SerializableCascadelessStrictDeadlock-free
Basic 2PL
Strict 2PL
Rigorous 2PL
Conservative 2PL

Section 06

Deadlocks — The Dark Side of Locking

The Narrow Bridge
Two cars approach a single-lane bridge from opposite ends. Each refuses to back up. Neither can proceed. They will sit there forever unless someone intervenes. That is a deadlock: two transactions each holding a lock the other needs, each waiting for the other to release first.

Classic Deadlock Example

TimeT1T2
t1lock-X(A)
t2lock-X(B)
t3lock-X(B) — WAIT
t4lock-X(A) — WAIT
💥 DEADLOCK! Both waiting forever.
Wait-For Graph — Deadlock Detected
T1 waits for B T2 waits for A T1 T2

A cycle in the Wait-For graph = deadlock. The DBMS breaks it by aborting (rolling back) one transaction — the "victim".

🛠️ Deadlock Handling Strategies
Detect
Wait-For Graph: periodically check for cycles. If found, abort the cheapest transaction (the victim). Used by most engines (PostgreSQL, MySQL/InnoDB).
Prevent
Wait-Die / Wound-Wait: use transaction timestamps to decide who waits and who aborts. No deadlock can ever form. Higher abort rate as a trade-off.
Timeout
If a transaction waits longer than a threshold, assume deadlock and abort it. Simple but imprecise — can kill transactions that are just slow.
Avoid
Conservative 2PL: lock everything upfront so circular waits are impossible. Deadlock-free, but hard to implement and limits concurrency.

Section 07

Numerical 1 — Does This Transaction Follow 2PL?

Problem: Determine whether each lock sequence follows Two-Phase Locking.

Sequence A

T1's lock sequence
lock-S(A), lock-X(B), lock-S(C), unlock(A), unlock(B), unlock(C)
✅ Solution
Growing
lock-S(A), lock-X(B), lock-S(C) — only acquisitions.
Lock Point
After lock-S(C) — the last lock acquired.
Shrinking
unlock(A), unlock(B), unlock(C) — only releases.
Verdict
✓ YES — follows Basic 2PL. No lock is acquired after the first unlock.

Sequence B

T2's lock sequence
lock-S(A), lock-X(B), unlock(A), lock-S(C), unlock(B), unlock(C)
✅ Solution
Growing?
lock-S(A), lock-X(B) — so far so good.
Problem
unlock(A) is a release → shrinking has begun. But then lock-S(C) appears — a new acquisition after a release!
Verdict
✗ NO — violates 2PL. A lock was acquired after a lock was released. The transaction has no clean growing/shrinking boundary.

Section 08

Numerical 2 — Strict 2PL Walkthrough

Two transactions T1 (transfers ₹500 from A to B) and T2 (reads A+B) run under Strict 2PL. Show the lock sequence.

TimeT1T2Locks Held
t1lock-X(A)T1: X(A)
t2R(A)=1000
t3W(A)=500
t4lock-X(B)T1: X(A), X(B)
t5lock-S(A) — WAITT2 blocked by T1's X(A)
t6R(B)=2000waiting…
t7W(B)=2500waiting…
t8Commit → unlock allT1 releases X(A), X(B)
t9lock-S(A) GRANTEDT2: S(A)
t10R(A)=500
t11lock-S(B)T2: S(A), S(B)
t12R(B)=2500
t13Commit → unlock allclean
Observations

T1 holds all X-locks until commit (step 8) — satisfying Strict 2PL. T2 is forced to wait at t5 because the S-lock on A is incompatible with T1's X-lock. Once T1 commits, T2 reads committed values (500 + 2500 = 3000 = original 3000). The result is consistent: no cascading abort and no lost update.


Section 09

Wait-Die & Wound-Wait — Deadlock Prevention

These two timestamp-based protocols use transaction age to decide who gets priority when a lock conflict arises. Older = higher priority.

🕐 Wait-Die (Non-Preemptive)
RequesterAction
Older requests lock held by YoungerWAIT (older is patient)
Younger requests lock held by OlderDIE (younger aborts, retries later with same timestamp)
🔨 Wound-Wait (Preemptive)
RequesterAction
Older requests lock held by YoungerWOUND (younger is forced to abort)
Younger requests lock held by OlderWAIT (younger is patient)
💡
Memory Trick

Wait-Die: the older transaction waits, the younger dies. Wound-Wait: the older transaction wounds (kills) the younger, the younger waits for the older. In both schemes, the younger transaction is the one that gets aborted — it always re-enters with its original timestamp so it eventually becomes old enough to succeed.


Section 10

Academic View vs Industry View

🎓 Academic Lens
AspectDetail
FocusProofs: 2PL ⇒ conflict serializable
Exam topicsLock sequences, identify 2PL violations, deadlock graphs
VariantsBasic, Strict, Rigorous, Conservative — know the guarantees
HierarchyBasic 2PL ⊂ Strict ⊂ Rigorous ⊂ Conservative
🏭 Industry Lens
AspectDetail
DefaultMost engines use Strict or Rigorous 2PL
DeadlocksDetected via wait-for graph; victim chosen by least work done
Lock granularityRow-level (InnoDB), page-level, table-level — finer = more concurrency
MVCC alternativePostgreSQL / Oracle use MVCC + SSI instead of lock-blocking for reads
AdviceKeep transactions short; access items in consistent order to reduce deadlocks
📊
The Modern Reality: Locks + MVCC Together

Most production databases (PostgreSQL, MySQL/InnoDB, SQL Server) combine lock-based and MVCC strategies. Reads use MVCC snapshots (no read locks needed, so readers never block writers), while writes still acquire locks (typically row-level X-locks held until commit, i.e. Strict 2PL). This hybrid gives the serializability guarantee of 2PL with the read-concurrency advantage of MVCC.


Section 11

Rapid-Fire Concept Check

QuestionAnswer
Two types of locksShared (S) and Exclusive (X)
Compatible pair?S-S only; all others block
What 2PL guaranteesConflict serializability
The two phasesGrowing (acquire) then Shrinking (release)
The lock pointInstant last lock is acquired
What Strict 2PL addsHold X-locks to commit → cascadeless
What Rigorous 2PL addsHold ALL locks to commit → strict schedules
Which variant is deadlock-free?Conservative 2PL
Cycle in wait-for graph meansDeadlock
Wait-Die: younger requests older's lock?Younger dies (aborts)
Wound-Wait: older requests younger's lock?Older wounds (kills younger)
Most common engine approach todayStrict 2PL + MVCC hybrid

Section 12

Golden Rules

🔒 Locking & Two-Phase Locking — Non-Negotiable Rules
1
The lock compatibility matrix has exactly one compatible pair: S-S. Every other combination (S-X, X-S, X-X) forces the requesting transaction to wait.
2
Two-Phase Locking means: once you release any lock, you may never acquire another. The lock-count graph is always a single-peaked hill.
3
2PL ⇒ conflict serializable. The equivalent serial order is the order of transactions' lock points. This is provable and always tested in exams.
4
Basic 2PL does not prevent cascading aborts or deadlocks. You need Strict 2PL (hold X-locks to commit) to eliminate cascading aborts.
5
Rigorous 2PL (hold all locks to commit) gives strict schedules — the strongest recovery guarantee short of serial execution.
6
A cycle in the wait-for graph = deadlock. The DBMS must detect and break it by aborting a victim. Wait-Die and Wound-Wait prevent deadlocks by using timestamps to decide who waits and who aborts.
7
In real systems, keep transactions short and access items in a consistent global order. Both practices dramatically reduce deadlock frequency and lock wait times.