✏️ Explanatory Question
Level: Very Hard — Deep InnoDB internals; gap locks confuse even experienced developers because they lock rows that don't exist yet.
SELECT ... WHERE age BETWEEN 20 AND 30 FOR UPDATE. Transaction B then tries to INSERT a brand-new employee with age = 25 — and it hangs, even though that row doesn't exist yet and A never touched it. Why is a non-existent row locked?
employees (indexed on age)| emp_id | age |
|---|---|
| 1 | 18 |
| 2 | 22 |
| 3 | 28 |
| 4 | 35 |
The cause: Under REPEATABLE READ, InnoDB uses next-key locks (a row lock + a gap lock on the range before it) to prevent phantom reads. When A locks age BETWEEN 20 AND 30, it doesn't just lock rows 22 and 28 — it locks the gaps around them (18–22, 22–28, 28–35). Any INSERT into those gaps (like age 25) must wait.
| Lock Type | What It Locks | Prevents |
|---|---|---|
| Record Lock | A single index row | Update/delete of that row |
| Gap Lock | The space BETWEEN rows (no rows) | Inserts into the gap |
| Next-Key Lock | A row + the gap before it | Both (phantom protection) |
-- Txn A: range lock under REPEATABLE READ (default)
START TRANSACTION;
SELECT * FROM employees
WHERE age BETWEEN 20 AND 30
FOR UPDATE;
-- Locks rows 22, 28 AND the gaps around the 20-30 range
-- Txn B: tries to insert into the locked gap -> BLOCKS
START TRANSACTION;
INSERT INTO employees (emp_id, age) VALUES (5, 25); -- HANGS / waits
-- Waits until Txn A commits or rolls back
FOR UPDATE when you only need specific rows.-- Under READ COMMITTED, gap locks are mostly disabled
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT * FROM employees WHERE age BETWEEN 20 AND 30 FOR UPDATE;
-- Only rows 22 and 28 are locked; the gaps are NOT
-- Now Txn B's INSERT of age 25 SUCCEEDS (no gap lock blocking it)
-- Trade-off: phantom reads become possible