Module 17: Transactions & Concurrency Control · Lesson 4 of 7
A deadlock is two (or more) transactions each holding a lock the other needs, with neither able to proceed. Locking (Lesson 03) makes deadlocks possible; this lesson covers how they happen, how MySQL resolves them automatically, and — more importantly — how to design around them so they’re rare rather than routine.
SHOW ENGINE INNODB STATUS deadlock reportLocking exists to make concurrent writes safe (Lesson 03), but locking two or more resources across multiple statements creates a new risk: if two transactions each need both resources but acquire them in opposite order, they can each end up holding one and waiting for the other — permanently, without intervention. A database must detect this and break the cycle, because neither transaction will ever release its lock on its own.
A “transfer budget between departments” operation that locks the source department row, then the destination department row, will deadlock against a second transfer running in the opposite direction (source and destination swapped) if both transactions started at roughly the same time and each grabbed its first lock before either could grab its second. This isn’t a rare edge case in any system with symmetric operations between two resources — it’s a predictable consequence of not imposing a consistent lock order.
-- Session A -- Session B
START TRANSACTION; START TRANSACTION;
UPDATE departments SET dept_name = 'A1' UPDATE departments SET dept_name = 'B1'
WHERE dept_id = 4; -- locks dept_id=4 WHERE dept_id = 7; -- locks dept_id=7
UPDATE departments SET dept_name = 'A2' UPDATE departments SET dept_name = 'B2'
WHERE dept_id = 7; -- BLOCKS, waiting WHERE dept_id = 4; -- BLOCKS, waiting
-- (Session B holds this lock) -- (Session A holds this lock)
-- Neither can proceed: A waits for the lock B holds on dept_id=7,
-- B waits for the lock A holds on dept_id=4. This is the cycle.
-- MySQL's deadlock detector identifies this and kills ONE of the
-- two transactions automatically (typically the one with less
-- rollback work), returning:
-- ERROR 1213 (40001): Deadlock found when trying to get lock;
-- try restarting transaction
The survivor’s transaction proceeds normally once the victim rolls back and releases its locks. The victim’s application code must catch this error and decide whether to retry.
SHOW ENGINE INNODB STATUS\G
Look at the LATEST DETECTED DEADLOCK section: it lists each
transaction involved, the statement it was running, the lock(s) it
held, and the lock it was waiting for — reading these two transaction
blocks side by side directly shows you the cycle (what each held vs.
what each wanted), which is the fastest way to identify exactly which
two resources need lock-order discipline.
emp_id values in
ascending order within a multi-row operation) — this is a specific
case of “consistent lock ordering.”MySQL: Detects deadlocks quickly (in normal operation, near-
immediately upon the cyclic wait forming) via InnoDB’s built-in
deadlock detector, and automatically rolls back one transaction —
typically the one InnoDB estimates has done less work, to minimize
rollback cost. No configuration is required for basic detection;
innodb_deadlock_detect can be disabled in very high-concurrency
setups where the detector itself becomes a bottleneck, at the cost of
then relying purely on lock wait timeout instead.
PostgreSQL: Also detects deadlocks automatically and aborts one
transaction with a deadlock_detected error, conceptually similar,
though implementation details (detection interval configurable via
deadlock_timeout) differ from MySQL’s near-immediate detection.
SQL Server: Also has automatic deadlock detection, and additionally
lets you set DEADLOCK_PRIORITY per session to influence which
transaction SQL Server chooses as the victim — a level of control
MySQL doesn’t expose the same way.
ERROR 1213
surface as an unhandled application error instead of catching it
and retrying the transaction.UPDATEs should
process rows in a consistent, predictable order (e.g., ORDER BY
the primary key) where the engine and query shape allow it.SHOW ENGINE INNODB STATUS\G — read the LATEST DETECTED
DEADLOCK section first; it directly names the two transactions,
what each held, and what each wanted.innodb_print_all_deadlocks to log every deadlock (not just
the latest) to the error log — invaluable for diagnosing a
deadlock that recurs intermittently under production load but isn’t
easily reproduced on demand.Deadlock frequency generally rises with concurrent write volume against overlapping resources and with transaction length (more time holding a first lock before acquiring a second widens the collision window). This is illustrative reasoning: the practical response is not “avoid all multi-row transactions,” but rather enforce consistent lock ordering and keep transactions as short as the business operation allows, since both directly shrink the window in which a cycle can form.
Oracle: Automatic deadlock detection with a comparable
victim-selection approach; Oracle’s error is ORA-00060: deadlock
detected while waiting for resource.
SQLite: Its coarser table/database-level locking model (Lesson 03)
makes classic row-level cyclic deadlocks largely inapplicable in the
same form — contention manifests more as SQLITE_BUSY from the
single-writer constraint than as a multi-row lock cycle.
DuckDB: Not designed around the high-concurrency multi-writer patterns that produce classic deadlocks; not a primary concern for DuckDB’s typical analytical, single-process usage.
A deadlock is a cycle of transactions each waiting on a lock the next
one holds. MySQL detects this automatically and kills one transaction
to break the cycle, returning ERROR 1213. The primary engineering
defense is consistent lock ordering across every code path touching
the same resources, backed by short transactions and application-level
retry logic for the (expected, not exceptional) deadlock errors that
still occur under real concurrent load.
departments dept_id 4 and 7. Observe which session
receives ERROR 1213.dept_id first, regardless of which “direction”
the business operation is conceptually going. Confirm the deadlock
no longer occurs.innodb_lock_wait_timeout” is not a fix for deadlocks specifically
(connect this back to Lesson 03, §7 on what a lock wait timeout
actually is).(Some challenges are scaffolded in this lesson’s .sql file. The cross-lesson problem set, with full solutions, is in PRACTICE_PROBLEMS.md and SOLUTIONS.sql.)
Previous: Lesson 03 — Locking Mechanisms · Next: Lesson 05 — MVCC