In a relational database engine, what is the difference between a shared lock and an exclusive lock, and which operations acquire each one?
answer
- S+S compatible, S+X and X+X are not
- X held to commit; S may release at statement end
- Writes take X, always
- MVCC: plain SELECT takes no row lock
- Lock manager = hash table of holders and waiters
basics
~20 sA shared (S) lock lets other shared locks in but blocks exclusive ones — many readers, no writer. An exclusive (X) lock excludes everything, readers and writers alike. Writes take X on the rows they modify; reads take S only in lock-based (non-MVCC) reading.
solid answer
~50 sLocks have **modes**, and modes have a compatibility matrix. A **shared (S)** lock is compatible with other S locks but not with X: many transactions can hold S on the same row simultaneously, and any writer must wait. An **exclusive (X)** lock is compatible with nothing: while it is held, no other transaction may read-with-lock or write that row. Writes (`UPDATE`, `DELETE`, and the inserted row) always take X on the affected rows and hold it until commit or rollback, which is why two transactions writing the same row serialize. Reads take S only when the engine reads *through locks* — a pure lock-based engine, or SQL Server without read-committed snapshot. The important nuance: in MVCC engines (PostgreSQL, Oracle, InnoDB), a plain `SELECT` takes **no row lock at all** — it reads an older committed version. So "readers block writers" is a property of lock-based reading, not of relational databases in general. Writer-vs-writer conflict on X locks is universal.
go deeper
State the compatibility matrix and the mapping — reads take S, writes take X, S is compatible only with S. That alone is a passing answer at screening level.
Add duration: X locks live until commit, S locks may be released at statement end depending on isolation level, and explain why the asymmetry exists.
Lead with the engine distinction: MVCC removes read locks entirely for plain SELECTs, so the real contention you tune for is writer-versus-writer X conflict plus explicit FOR UPDATE requests.
Frame it as a design axis — lock-based reading buys you read stability with contention, MVCC buys you read concurrency at the cost of version storage, vacuum/undo pressure and write-skew anomalies. Say which you would pick for a given workload and why.
## What a lock is A lock is not a physical thing attached to a row. It is an entry in the engine's in-memory **lock manager** — a hash table keyed by resource identifier (table id, page id, row id / key value) whose value is a list of holders and waiters, each with a **mode**. "Locking a row" means inserting an entry saying *transaction T holds mode M on resource R*. Everything else — blocking, queueing, escalation — is bookkeeping around that table. ## The two base modes **Shared (S)**, sometimes called a read lock: taken by a transaction that wants to observe a value and guarantee nobody changes it while it looks. Multiple transactions may hold S on the same resource at once, because reading does not disturb anyone else's read. **Exclusive (X)**, the write lock: taken by a transaction that intends to modify the resource. It is incompatible with every other mode, including another X. Only one holder at a time. ## The compatibility matrix The matrix is the whole rule, and it is worth memorising: ``` requested S requested X held S yes no held X no no ``` A request that is not compatible with an existing holder waits in the resource's queue. Most engines queue **FIFO with a fairness rule**: once an X request is queued, newly arriving S requests also wait behind it, otherwise a stream of readers could starve the writer forever. ## Who takes which mode - `UPDATE`, `DELETE`: X on each row actually modified. In a well-indexed plan that is the qualifying rows; in a table scan the engine may briefly lock and release rows it inspects and rejects, and some engines lock every row it *touches* — one reason a missing index turns a small update into a wide blocking event. - `INSERT`: X on the new row (usually trivially uncontended, since nobody else can see it yet) plus locks on unique-index entries, which is how two concurrent inserts of the same key end up blocking each other. - Plain `SELECT`: this is where engines diverge, see below. - Explicit `SELECT ... FOR UPDATE` / `FOR SHARE`: X-style and S-style row locks respectively, requested deliberately by the application. - DDL: table-level X (or an equivalent exclusive metadata lock), which is why an `ALTER TABLE` can freeze an entire hot table. ## MVCC changes what readers do In a **multiversion (MVCC)** engine — PostgreSQL, Oracle, MySQL/InnoDB, SQL Server with read-committed snapshot isolation enabled — an ordinary `SELECT` acquires **no row locks whatsoever**. It reads the version of each row that was committed as of its snapshot, walking the undo log or the version chain if the current version is too new. Readers therefore never block writers and writers never block readers. In a pure lock-based engine, or SQL Server in its default read-committed-locking mode, a `SELECT` does take S locks, and there readers genuinely do block writers. Saying flatly "readers block writers in SQL" is a common interview stumble; the accurate statement is "under lock-based reading, yes; under MVCC, no — but writers always block writers." ## Lock duration Modes describe *what* conflicts; duration describes *how long*. X locks on modified rows are held until the transaction ends — releasing them early would let another transaction read data that could still roll back. S locks are cheaper and shorter-lived: at READ COMMITTED a lock-based engine typically releases the S lock as soon as the row has been read, while at REPEATABLE READ or SERIALIZABLE it holds it to commit so the row cannot change underneath a re-read. ## Beyond S and X Real engines add modes on top of these two. **Update (U)** is a read-with-intent-to-write mode used to avoid conversion deadlocks. **Intent modes (IS, IX, SIX)** are placed on the coarse objects — table, page — above a finely locked row so an engine can tell instantly whether a table-level request would conflict with something buried inside. These are refinements of the same compatibility-matrix idea, not replacements for it. ## What interviewers listen for They want the compatibility matrix stated correctly, the pairing of S with reads and X with writes, and — as the sign of depth — the observation that MVCC removes read locks entirely for ordinary `SELECT`s while leaving the writer-vs-writer X conflict fully intact. Adding that X locks live until commit while S locks may not shows you understand that mode and duration are independent dials.
- If plain SELECTs take no locks under MVCC, what still causes two transactions to block each other?Writer-versus-writer conflict on exclusive row locks. If both transactions update the same row, the second waits on the first's X lock until it commits or rolls back. Explicit locking requests (`SELECT ... FOR UPDATE`), unique-index entries for the same key, and table-level locks taken by DDL are the other common blockers.
- Why can't the engine release an exclusive lock as soon as the UPDATE statement finishes?Because the transaction may still roll back. Releasing early would let another transaction read or overwrite a value that never becomes committed, producing a dirty read or a lost update. Holding write locks until commit is exactly what makes recovery and isolation sound, and it is the rule strict two-phase locking encodes.
- Two transactions both hold a shared lock on the same row and both then try to upgrade to exclusive. What happens?Each waits for the other to release its S lock, so neither can proceed — a conversion deadlock. The engine's deadlock detector will pick a victim and abort it. Engines that offer an update (U) lock mode let a transaction signal read-with-intent-to-write up front and avoid the situation entirely.
A library reading room: any number of people can read the same reference copy at once (shared), but the moment someone checks it out to annotate it (exclusive) nobody else may read or write it until it comes back.
saying these in an interview costs you the question
- "Readers always block writers in a relational database" — untrue under MVCC, where plain SELECTs take no row locks
- Believing two transactions can hold X on the same row if they touch different columns — row locks are per-row, not per-column, in mainstream engines
- Thinking a shared lock prevents other transactions from reading the row — it prevents writes, not reads
- Assuming locks are stored on disk with the row rather than in an in-memory lock manager
- Claiming SELECT never takes locks in any engine — SQL Server's default read-committed mode and explicit FOR SHARE both do