✏️ Explanatory Question
Level: Very Hard — A real on-call incident; you must reason about the exact interleaving that causes the circular wait.
SELECT ... FOR UPDATE before updating balances. Under load, you're getting "ERROR 1213: Deadlock found when trying to get lock." a few times per minute. The logic looks correct. Why are transactions deadlocking, and how do you fix it?
accounts| acc_id | owner | balance |
|---|---|---|
| 1 | Rumman | 10000 |
| 2 | Krushna | 5000 |
Two transfers happen at once, in opposite directions. Each locks accounts in the order the money flows:
| Time | Txn A (transfer 1 → 2) | Txn B (transfer 2 → 1) |
|---|---|---|
| t1 | Locks acc 1 (FOR UPDATE) | Locks acc 2 (FOR UPDATE) |
| t2 | Wants acc 2 → waits | Wants acc 1 → waits |
| t3 | Circular wait → DEADLOCK (InnoDB kills one) | |
The root cause: The transactions acquire locks in inconsistent order. Txn A locks 1→2; Txn B locks 2→1. Each holds what the other needs. InnoDB detects the cycle and rolls back a "victim."
-- Txn A: transfer from acc 1 to acc 2
START TRANSACTION;
SELECT balance FROM accounts WHERE acc_id = 1 FOR UPDATE; -- locks 1
SELECT balance FROM accounts WHERE acc_id = 2 FOR UPDATE; -- wants 2
UPDATE accounts SET balance = balance - 100 WHERE acc_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE acc_id = 2;
COMMIT;
-- Txn B: transfer from acc 2 to acc 1 (locks in the OPPOSITE order)
START TRANSACTION;
SELECT balance FROM accounts WHERE acc_id = 2 FOR UPDATE; -- locks 2
SELECT balance FROM accounts WHERE acc_id = 1 FOR UPDATE; -- wants 1
-- ... DEADLOCK ...
-- FIX: lock accounts in a DETERMINISTIC order (e.g., ascending acc_id)
-- regardless of transfer direction. Both transactions now agree on order.
START TRANSACTION;
-- Lock the LOWER id first, always
SELECT balance FROM accounts
WHERE acc_id IN (1, 2)
ORDER BY acc_id -- deterministic lock acquisition order
FOR UPDATE;
-- Now safely apply the transfer (direction handled in the UPDATEs)
UPDATE accounts SET balance = balance - 100 WHERE acc_id = 1; -- sender
UPDATE accounts SET balance = balance + 100 WHERE acc_id = 2; -- receiver
COMMIT;
min(a,b) then max(a,b)).
SHOW ENGINE INNODB STATUS → "LATEST DETECTED DEADLOCK".-- Application-side retry wrapper:
-- FOR attempt IN 1..3:
-- TRY:
-- START TRANSACTION;
-- ... locking + updates in consistent order ...
-- COMMIT;
-- BREAK; -- success
-- CATCH deadlock (error 1213):
-- ROLLBACK;
-- SLEEP(random 10-100ms); -- backoff, then retry