In the two-phase locking (2PL) concurrency-control protocol used by relational database engines, what are the two phases, what rule must a transaction obey in each, and what is the transaction's "lock point"?
answer
- Grow then shrink — one-way door
- Lock point = last acquire, before first release
- Upgrade S→X counts as acquiring
- 2PL ⇒ conflict-serializable, ⇏ deadlock-free
- Strict = hold X locks to commit; rigorous = hold all
basics
~20 sGrowing phase: the transaction may take locks but release none. Shrinking phase: it may release locks but take no new ones. The lock point is the instant between them, where it holds every lock it will ever hold.
solid answer
~50 s2PL constrains the **order** in which a transaction acquires and releases locks, not which locks it takes. - **Growing phase** — the transaction acquires locks (including upgrades from shared to exclusive) and releases nothing. - **Shrinking phase** — once it releases its first lock it may release more, but it can never acquire again. The boundary is the **lock point**: the moment it holds its maximal lock set. Ordering all transactions by their lock points yields a serial order equivalent to the actual schedule, which is why 2PL produces conflict-serializable schedules. What 2PL does *not* give you: it is not deadlock-free (two transactions can still wait on each other), it says nothing about lock modes or granularity, and plain 2PL still allows dirty reads and cascading aborts — that needs **strict 2PL**, which holds write locks until commit. Real engines almost always implement a strict or rigorous variant.
code
text · 6 linesT1: lock-S(A) lock-X(B) lock-X(C) | unlock(A) unlock(B) unlock(C)
<-------- growing --------> ^ <-------- shrinking ------->
lock point
ILLEGAL under 2PL:
T1: lock-S(A) unlock(A) lock-X(B) <- acquires after releasinggo deeper
Be able to state the two phases and the one-way rule cleanly, give the lock point, and say the payoff is serializable schedules.
Add the variants — basic vs strict vs rigorous — and explain that weaker isolation levels are deliberate relaxations of the read-lock side of the rule.
Frame it as ordering discipline versus mode/granularity, note that 2PL is sufficient but not necessary for serializability, and connect strictness to recovery and rollback correctness.
Position 2PL as one point in the concurrency-control design space against MVCC and optimistic schemes, and talk about what the protocol costs in lock-hold time and throughput at high contention.
## The problem 2PL solves Concurrent transactions interleave their reads and writes. Some interleavings produce results that no serial (one-transaction-at-a-time) execution could produce: lost updates, reads of half-finished work, an aggregate that sees one account before a transfer and the other after it. **Concurrency control** is the engine subsystem that restricts interleavings to safe ones. Two-phase locking is the classic *pessimistic* answer: guard each data item with a lock, and constrain the **order** in which a transaction acquires and releases those locks. ## The two phases Every transaction under 2PL passes through exactly two phases: 1. **Growing (expanding) phase** — the transaction may acquire locks. It may not release any. Upgrading a shared lock to an exclusive one counts as an acquisition, so it too must happen here. 2. **Shrinking (contracting) phase** — the transaction may release locks. It may not acquire any new lock, ever again. The transaction enters the shrinking phase the instant it releases its first lock, and the transition is one-way. The boundary is called the **lock point**: the moment at which the transaction holds its maximal set of locks — after the last acquisition, before the first release. Notice what the rule does *not* say. It says nothing about *which* items are locked, in what **mode** (shared/exclusive), or at what **granularity** (row, page, table). Those are orthogonal choices. 2PL is purely a discipline about acquire-then-release ordering. ## Why the discipline is the whole trick At its lock point a transaction simultaneously holds a claim on everything it will ever touch. You can therefore pretend the entire transaction happened atomically at that instant, and order transactions by lock point to obtain an equivalent serial schedule. The failure mode when you break the rule is easy to see. Suppose T1 reads A, releases A's lock, then later locks and reads B. In between, T2 updates both A and B and commits. T1 has now seen A in its pre-T2 state and B in its post-T2 state — a combination no serial order of T1 and T2 can produce, so any invariant linking A and B (say, "the two balances sum to 100") can be reported broken. Being two-phase forbids exactly this: once T1 lets go of anything, it may not reach for B. ## The standard variants All of these are still two-phase; they differ only in how late the shrinking phase happens. - **Basic (plain) 2PL** — release each lock as soon as the transaction knows it needs no more. Maximum concurrency, but it permits other transactions to read uncommitted data, which allows **cascading aborts** and non-recoverable schedules. - **Conservative (static) 2PL** — acquire *all* locks atomically before the transaction begins, which requires predeclaring the read/write set. This is deadlock-free (a transaction that cannot get everything waits without holding anything), but it needs advance knowledge of what will be touched and holds locks for the whole duration. Rare in general-purpose engines; used in some deterministic and stored-procedure-only systems. - **Strict 2PL** — hold all *exclusive* (write) locks until commit or abort; shared locks may be released earlier. This is what makes rollback and recovery sane. - **Rigorous, a.k.a. strong strict 2PL (SS2PL)** — hold *all* locks, shared and exclusive, until commit. The serialization order then equals the commit order, which is a very convenient property for replication and recovery. This is what most commercial lock-based engines actually implement. Holding everything to commit is a degenerate two-phase schedule: the growing phase is the whole transaction and the shrinking phase is a single instant at commit. ## What 2PL does not give you - **It is not deadlock-free** (except the conservative variant). T1 locks A and wants B while T2 locks B and wants A; both wait forever. Engines bolt on waits-for-graph detection or lock timeouts separately. - **It is not maximally concurrent.** 2PL is sufficient but not necessary for serializability: some perfectly serializable schedules are rejected because they would require re-acquiring a lock after a release. - **It does not by itself make schedules recoverable.** That is the job of the strict variant. ## Where you see it in a running system In a lock-based engine a read takes a shared lock and a write takes an exclusive one; locks are acquired as execution proceeds (so the growing phase is spread across the statements) and dropped at COMMIT or ROLLBACK. Weaker isolation levels are, in effect, deliberate violations of the two-phase rule for read locks: at READ COMMITTED an engine typically takes a shared lock for the duration of the read and drops it immediately, so the transaction is no longer two-phase for reads — which is precisely why non-repeatable reads become possible there. Seeing isolation levels as "how much of the 2PL discipline you keep" is usually what an interviewer is fishing for after the definition.
- Does two-phase locking prevent deadlocks?No. Basic, strict and rigorous 2PL all allow deadlock: two transactions can each hold a lock the other is still waiting to acquire during their growing phases. Engines handle it separately with waits-for-graph detection and victim selection, or with lock-wait timeouts. Only conservative (static) 2PL, which grabs the entire lock set atomically up front, is deadlock-free — at the cost of needing the access set declared in advance.
- If a transaction upgrades a shared lock to an exclusive lock, which phase must that happen in?The growing phase. An upgrade is an acquisition of stronger rights, so doing it after any lock has been released would violate the protocol. This matters in practice because read-then-write patterns cause upgrade requests, and two transactions that both hold a shared lock and both request an upgrade deadlock immediately — which is why engines offer an update/intent-to-write mode taken during the initial read.
- Are all conflict-serializable schedules producible by 2PL?No — 2PL is sufficient but not necessary. There exist schedules that are conflict-serializable yet would require a transaction to acquire a lock after releasing one, so 2PL forbids them. That conservatism is the price of a purely local rule: each transaction obeys the protocol on its own, with no global schedule analysis, and serializability falls out anyway.
Shopping with a one-way exit: while you are in the store you can keep putting items in your cart, but the moment you put the first item back on a shelf you may never pick anything else up. That forced ordering means there is one instant where your cart holds everything you will ever have.
saying these in an interview costs you the question
- Saying 2PL means "two locks" or "locking in two rounds" rather than two ordering phases
- Claiming 2PL prevents deadlocks
- Describing plain 2PL as "take locks, hold them all until commit" — that is the strict/rigorous variant, not the base protocol
- Thinking a shared-to-exclusive upgrade is allowed during the shrinking phase
- Confusing the protocol with lock modes or granularity ("2PL means row-level locking")