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

Pessimistic vs Optimistic Locking

Database PHP 5.0+ Advanced
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' — semgrep and laravel-debugbar can help identify patterns but won't reliably catch the semantic problem of choosing the wrong locking strategy for a given conflict level. Lost updates from missing FOR UPDATE or retry storms from misapplied optimistic locking typically only surface under production load or careful code review.

e5 Effort Remediation debt — work required to fix once spotted

Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix describes choosing the right strategy, but retrofitting locking into existing code requires modifying repository methods, adding version columns to tables (with migrations), and implementing retry logic for optimistic locking. This spans database schema, model code, and application logic — a significant refactor within the data access layer.

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

Closest to 'persistent productivity tax' (b5). Locking strategy applies across web, cli, and queue-worker contexts per applies_to. Once a locking approach is embedded in your data access patterns, every future concurrent operation must consider it. Teams must understand when to use which strategy, and inconsistent application leads to subtle bugs. Not quite b7 because it's isolated to data mutation paths rather than shaping every change.

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

Closest to 'serious trap' (t7). The misconception explicitly states developers believe 'optimistic locking is always better because it avoids locks' — this contradicts how locking works in databases where optimistic locking under high contention causes expensive retry storms. Common_mistakes reinforce this: not retrying on failure, not incrementing atomically, using optimistic in high-contention scenarios. Developers familiar with general concurrency concepts often guess wrong about which strategy fits which scenario.

About DEBT scoring →

Also Known As

SELECT FOR UPDATE optimistic locking version column row locking

TL;DR

Pessimistic locking acquires a database lock upfront preventing conflicts. Optimistic locking detects conflicts at write time using a version column — trading lock overhead for conflict detection.

Explanation

Pessimistic locking (SELECT ... FOR UPDATE) locks the row immediately — no other transaction can modify it until the lock is released. Best for high-conflict scenarios (inventory, account balances). Optimistic locking uses a version counter or timestamp — read the row, remember the version, update only if version unchanged (UPDATE ... WHERE version = :original). If another transaction modified it, the update affects 0 rows — the application retries. Best for low-conflict scenarios (profile updates, settings).

Diagram

sequenceDiagram
    participant T1 as Transaction 1
    participant DB as Database
    participant T2 as Transaction 2
    Note over T1,T2: Pessimistic Locking
    T1->>DB: SELECT ... FOR UPDATE - locks row
    T2->>DB: SELECT ... FOR UPDATE
    DB-->>T2: WAIT - row locked
    T1->>DB: UPDATE + COMMIT
    DB-->>T2: Lock released
    Note over T1,T2: Optimistic Locking
    T1->>DB: SELECT balance version=5
    T2->>DB: SELECT balance version=5
    T1->>DB: UPDATE WHERE version=5 - sets version=6
    T2->>DB: UPDATE WHERE version=5 - 0 rows conflict

Common Misconception

Optimistic locking is always better because it avoids locks — optimistic locking causes expensive retries in high-conflict scenarios; use pessimistic locking when conflicts are frequent.

Why It Matters

Using optimistic locking for a bank transfer (high-conflict) causes constant retries; using pessimistic locking for a profile save (low-conflict) adds unnecessary serialisation — choosing correctly matters.

Common Mistakes

  • Not retrying on optimistic locking failure — when the update affects 0 rows, the application must reload and retry.
  • Pessimistic locking without a transaction — locks are released at transaction end; a long-running operation holds them for too long.
  • Not incrementing the version column atomically — UPDATE ... SET version = version + 1 must be in the same statement.
  • Optimistic locking in high-contention scenarios — retry storms under load can degrade performance worse than pessimistic locks.

Code Examples

✗ Vulnerable
// No locking — race condition:
$balance = $db->query('SELECT balance FROM accounts WHERE id = ?', [$id])->fetchColumn();
if ($balance >= $amount) {
    // Another process can read same balance here!
    $db->query('UPDATE accounts SET balance = balance - ? WHERE id = ?', [$amount, $id]);
    // Both processes deduct — balance goes negative
}
✓ Fixed
// Pessimistic — lock the row before reading:
$db->beginTransaction();
$balance = $db->query('SELECT balance FROM accounts WHERE id = ? FOR UPDATE', [$id])->fetchColumn();
if ($balance >= $amount) {
    $db->query('UPDATE accounts SET balance = balance - ? WHERE id = ?', [$amount, $id]);
}
$db->commit();

// Optimistic — detect conflict at write time:
$row = $db->query('SELECT balance, version FROM accounts WHERE id = ?', [$id])->fetch();
$affected = $db->query('UPDATE accounts SET balance = balance - ?, version = version + 1 WHERE id = ? AND version = ?',
    [$amount, $id, $row['version']])->rowCount();
if ($affected === 0) throw new ConflictException('Retry required');

Added 15 Mar 2026
Edited 22 Mar 2026
Views 99
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings S 0 pings M 2 pings T 2 pings W 0 pings T 1 ping F 0 pings S 0 pings S 1 ping M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 2 pings W 1 ping T 0 pings F 0 pings S 0 pings S 0 pings M 3 pings T 0 pings W 0 pings T 0 pings F 0 pings S 1 ping S 1 ping M
Google 1
SEMrush 1
Google 10 Amazonbot 9 Ahrefs 7 Bing 6 SEMrush 6 PetalBot 6 ChatGPT 5 Scrapy 4 Perplexity 3 Applebot 2 Meta AI 1 Twitter/X 1
crawler 56 crawler_json 4
🧱 FUNDAMENTALS — new to this? Start with the ground floor.
Database database A database is an organized collection of data stored electronically, designed so programs can efficiently retrieve, add, update, and delete information.

Nearly every application needs to remember information between sessions. Databases provide the reliable, fast, and organized storage that makes persistent data possible at any scale.

💡 Always use prepared statements with placeholders—never concatenate user input directly into database queries.

Ask Codex about Database →
DEV INTEL Tools & Severity
🟠 High ⚙ Fix effort: Medium
⚡ Quick Fix
Use optimistic locking (version column) for low-contention updates; use pessimistic locking (SELECT FOR UPDATE) only when conflicts are frequent and retries are expensive
📦 Applies To
PHP 5.0+ web cli queue-worker
🔗 Prerequisites
🔍 Detection Hints
Inventory or balance update without locking strategy; SELECT then UPDATE without FOR UPDATE causing lost updates
Auto-detectable: ✗ No semgrep laravel-debugbar
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: High ✗ Manual fix Fix: High Context: File Tests: Update
CWE-833


✓ schema.org compliant