Database Deadlocks
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');
}
References
Tags
🤝 Adopt this term
£79/year · your link shown here
Added
15 Mar 2026
Edited
22 Mar 2026
Views
29
🤖 AI Guestbook educational data only
|
|
Last 30 days
Agents 2
Perplexity 1
Amazonbot 1
No pings yesterday
Perplexity 8
Amazonbot 7
Ahrefs 2
Google 2
Unknown AI 2
ChatGPT 2
SEMrush 2
Also referenced
How they use it
crawler 23
crawler_json 2
Related categories
⚡
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