Deadlock
debt(d7/e5/b5/t7)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints indicate automated detection is 'no', and the code pattern 'lockForUpdate|SELECT.*FOR UPDATE' only identifies potential deadlock-prone code, not actual deadlocks. Deadlocks manifest at runtime under concurrent load — they require runtime testing or production monitoring to catch. No automated static tool can reliably detect them.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix mentions multiple interventions: ensuring consistent lock ordering across transactions, setting lock timeouts, adding retry logic with PDOException handling, and potentially switching to optimistic locking. This typically requires changes across multiple database-touching components to establish consistent ordering and retry patterns.
Closest to 'persistent productivity tax' (b5). Deadlock-safe code imposes ongoing discipline: every developer writing concurrent database code must maintain consistent lock ordering conventions and handle retry logic. The applies_to shows this spans web, cli, and queue-worker contexts — anywhere concurrent database access occurs. It's not architectural-level burden but does slow development across multiple work streams.
Closest to 'serious trap' (t7). The misconception explicitly states developers think 'deadlocks only happen in multi-threaded programs' when database deadlocks are extremely common in PHP. This contradicts how developers familiar with single-threaded PHP expect things to work — they don't anticipate concurrency issues in a language without threads, missing that the database layer introduces concurrent access across requests.
TL;DR
Explanation
Classic deadlock: Thread A holds Lock 1, wants Lock 2. Thread B holds Lock 2, wants Lock 1. Both wait forever. Four conditions (Coffman): mutual exclusion, hold and wait, no preemption, circular wait. Breaking any one prevents deadlock. Strategies: (1) Lock ordering — always acquire locks in the same order. (2) Timeouts — acquire with timeout, retry. (3) Deadlock detection — detect cycles and break them. (4) Avoid nested locks — flat locking patterns. In PHP/databases: deadlocks occur in MySQL when two transactions update rows in different orders. MySQL auto-detects and kills one transaction — catch PDOException with SQLSTATE 40001.
Common Misconception
Why It Matters
Common Mistakes
- Not retrying on MySQL deadlock error (SQLSTATE 40001).
- Acquiring database row locks in different orders across transactions.
- Holding application-level locks while waiting for DB locks.
Code Examples
// Transaction A: locks user then order
// Transaction B: locks order then user
// → Deadlock
DB::transaction(function() use ($userId, $orderId) {
User::lockForUpdate()->find($userId);
Order::lockForUpdate()->find($orderId);
});
// Consistent lock order (always user before order):
DB::transaction(function() use ($userId, $orderId) {
$ids = [$userId, $orderId];
sort($ids); // Consistent order prevents deadlock
foreach ($ids as $id) { /* lock in order */ }
});
// Catch and retry deadlocks:
try {
DB::transaction(fn() => processOrder($orderId));
} catch (\PDOException $e) {
if ($e->getCode() === '40001') {
// Deadlock — retry once
DB::transaction(fn() => processOrder($orderId));
} else throw $e;
}