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

Database Deadlocks

Database PHP 5.0+ Advanced
debt(d5/e5/b5/t7)
d5 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches it' (d5). Deadlocks are detected by specialized monitoring tools like mysql-innodb-monitor, laravel-debugbar, and datadog as listed in detection_hints.tools. The database itself reports deadlock errors (MySQL 1213, PostgreSQL 40P01), but discovering the pattern of unhandled deadlocks requires these monitoring tools rather than static analysis or linters.

e5 Effort Remediation debt — work required to fix once spotted

Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix indicates multiple changes needed: consistent lock ordering across all transactions, keeping transactions short, and proper SELECT FOR UPDATE usage. While individual retry logic might be a few lines, ensuring consistent lock ordering 'across all transactions' requires reviewing and potentially modifying multiple transaction sites throughout the codebase.

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

Closest to 'persistent productivity tax' (b5). Deadlock handling applies to web, cli, and queue-worker contexts per applies_to. Once deadlocks start occurring in a high-concurrency system, every transaction-heavy feature must consider lock ordering and retry logic. This creates ongoing cognitive load for developers writing new transactional code, but doesn't quite reach b7 since it's confined to database-touching code paths rather than shaping all architectural decisions.

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

Closest to 'serious trap' (t7). The misconception explicitly states developers wrongly believe 'deadlocks are always application bugs' when in fact databases handle them automatically by rolling back. The real trap is that developers don't realize they must retry the rolled-back transaction — the database silently rolls back without completing the operation, and developers assume success or assume the database 'fixed' it. This contradicts how most error handling works (where catching an exception means handling it, not retrying the entire operation).

About DEBT scoring →

Also Known As

deadlock circular wait lock cycle

TL;DR

A situation where two or more transactions hold locks the other needs, each waiting indefinitely — resolved by the database killing one transaction.

Explanation

Deadlocks occur when transaction A holds a lock on row 1 and waits for row 2, while transaction B holds row 2 and waits for row 1. Databases detect this cycle and roll back the transaction with the smallest undo log (the 'victim'). The application must retry the victim transaction. Prevention strategies: acquire locks in a consistent order, keep transactions short, use SELECT ... FOR UPDATE to acquire all locks upfront.

Diagram

sequenceDiagram
    participant T1 as Transaction 1
    participant T2 as Transaction 2
    T1->>T1: LOCK account 1
    T2->>T2: LOCK account 2
    T1->>T1: Wait for account 2 - blocked
    T2->>T2: Wait for account 1 - blocked
    Note over T1,T2: DEADLOCK - circular wait
    T2-->>T2: DB kills T2 as victim
    T1->>T1: Gets account 2 lock
    T1->>T1: COMMIT

Common Misconception

Deadlocks are always application bugs — databases handle them automatically by rolling back one transaction, but the application must retry to complete the operation.

Why It Matters

Unhandled deadlock exceptions cause silent data inconsistency when the rolled-back transaction is not retried — in high-concurrency systems they happen regularly and must be expected.

Common Mistakes

  • Not catching and retrying deadlock exceptions (error code 1213 in MySQL, 40P01 in PostgreSQL).
  • Accessing rows in different orders in different transactions — consistent lock ordering prevents deadlocks.
  • Long transactions that hold locks for seconds instead of milliseconds.
  • Not using SELECT ... FOR UPDATE when a read-then-write sequence must be atomic.

Code Examples

✗ Vulnerable
// Inconsistent lock order — deadlock prone:
// Transaction A: lock user 1, then lock user 2
// Transaction B: lock user 2, then lock user 1 — deadlock!
$pdo->beginTransaction();
$pdo->query('SELECT ... FROM accounts WHERE id = ' . $fromId . ' FOR UPDATE');
$pdo->query('SELECT ... FROM accounts WHERE id = ' . $toId . ' FOR UPDATE');
✓ Fixed
// Consistent lock order — always lock lower ID first:
$pdo->beginTransaction();
$ids = [$fromId, $toId];
sort($ids); // Always acquire locks in ascending ID order
foreach ($ids as $id) {
    $pdo->query('SELECT ... FROM accounts WHERE id = ' . $id . ' FOR UPDATE');
}

Added 15 Mar 2026
Edited 22 Mar 2026
Views 62
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 1 ping T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 1 ping T 2 pings F 2 pings S 4 pings S 2 pings M 2 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 2 pings M 0 pings T 0 pings W
No pings yet today
No pings yesterday
Scrapy 10 Perplexity 8 Amazonbot 8 Ahrefs 4 Google 3 Unknown AI 2 ChatGPT 2 SEMrush 2 Claude 2 Bing 2 PetalBot 2 Meta AI 1
crawler 42 crawler_json 4
DEV INTEL Tools & Severity
🟠 High ⚙ Fix effort: High
⚡ Quick Fix
Acquire locks in a consistent order across all transactions; keep transactions short; use SELECT FOR UPDATE only on rows you will modify in the same transaction
📦 Applies To
PHP 5.0+ web cli queue-worker
🔗 Prerequisites
🔍 Detection Hints
Two transactions locking same rows in different order; long-running transactions; SELECT FOR UPDATE without immediate UPDATE
Auto-detectable: ✓ Yes mysql-innodb-monitor laravel-debugbar datadog
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: High Context: File Tests: Update
CWE-833


✓ schema.org compliant