← CodeClarityLab Home
Browse by Category
+ added · updated 7d
← Back to glossary

Deadlock

concurrency Intermediate
debt(d7/e5/b5/t7)
d7 Detectability Operational debt — how invisible misuse is to your safety net

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.

e5 Effort Remediation debt — work required to fix once spotted

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.

b5 Burden Structural debt — long-term weight of choosing wrong

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.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

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.

About DEBT scoring →

TL;DR

A deadlock occurs when two or more processes each hold a resource the other needs — both wait forever. Prevention requires consistent lock ordering or timeouts.

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

Deadlocks only happen in multi-threaded programs — database deadlocks are extremely common in PHP applications with concurrent transactions updating multiple rows.

Why It Matters

Database deadlocks silently fail requests in production unless caught and retried — they're a common source of mysterious 500 errors under load.

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

✗ Vulnerable
// 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);
});
✓ Fixed
// 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;
}

Added 23 Mar 2026
Views 44
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 1 ping F 1 ping S 2 pings S 0 pings M 0 pings T 0 pings W 1 ping T 0 pings F 3 pings S 1 ping S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 1 ping S 1 ping S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S
No pings yet today
No pings yesterday
Amazonbot 14 Perplexity 10 ChatGPT 3 Unknown AI 3 Google 2 Ahrefs 2 SEMrush 2 Majestic 1 Meta AI 1
crawler 36 crawler_json 1 pre-tracking 1
DEV INTEL Tools & Severity
🟠 High ⚙ Fix effort: Medium
⚡ Quick Fix
Always acquire locks in a consistent order. Set lock timeouts. Catch PDOException SQLSTATE 40001 and retry. Use optimistic locking to avoid locks entirely.
📦 Applies To
web cli queue-worker
🔗 Prerequisites
🔍 Detection Hints
lockForUpdate|SELECT.*FOR UPDATE
Auto-detectable: ✗ No
⚠ Related Problems
🤖 AI Agent
Confidence: Low False Positives: High ✗ Manual fix Fix: High Context: Function Tests: Update
CWE-833

✓ schema.org compliant