Optimistic Locking
Also Known As
optimistic concurrency control
version column pattern
compare and swap database
TL;DR
A concurrency pattern using a version column to detect conflicting concurrent writes — no row locks held between read and write.
Explanation
A version INT (or updated_at DATETIME) column is incremented on every UPDATE. The WHERE clause includes AND version = :expected — if rowCount() returns 0, another process changed the row first. Advantages: no lock contention, scales well for read-heavy workloads. Disadvantages: requires retry logic, degrades under high write contention on the same row. Preferred over pessimistic locking for most web application patterns.
Common Misconception
✗ Optimistic locking is always better than pessimistic locking. Under high write contention on the same row, optimistic locking causes excessive retries — pessimistic locking is more predictable in that case.
Why It Matters
Pessimistic locking holds locks during the full read-think-write cycle causing timeouts. Optimistic locking eliminates contention — conflicting writes are detected at commit time, not locked upfront.
Common Mistakes
- Not implementing retry logic — silently discarding the failed update.
- Using updated_at as the version — two updates within the same second both pass the check.
- Using optimistic locking for financial operations where a lost update is unacceptable.
Avoid When
- Avoid when conflict rate is high — retries become the dominant code path.
- Do not use for financial transfers where a missed update is a data integrity failure.
When To Use
- Use for read-heavy workflows where conflicts are infrequent.
- Use when holding locks between read and write is impractical — e.g. user edits a form for 30 seconds.
Code Examples
✗ Vulnerable
// Last write wins — concurrent change silently overwritten
$pdo->prepare('UPDATE products SET name = ? WHERE id = ?')->execute([$newName, $id]);
✓ Fixed
$stmt = $pdo->prepare('SELECT id, name, version FROM products WHERE id = ?');
$stmt->execute([$id]);
$product = $stmt->fetch();
$stmt = $pdo->prepare(
'UPDATE products SET name = ?, version = version + 1 WHERE id = ? AND version = ?'
);
$stmt->execute([$newName, $id, $product['version']]);
if ($stmt->rowCount() === 0) {
throw new ConcurrentModificationException('Row was modified by another process — retry');
}
Tags
🤝 Adopt this term
£79/year · your link shown here
Added
31 Mar 2026
Views
34
🤖 AI Guestbook educational data only
|
|
Last 30 days
Agents 0
No pings yet today
No pings yesterday
Perplexity 6
ChatGPT 6
Google 4
Unknown AI 2
Ahrefs 1
Also referenced
How they use it
crawler 17
crawler_json 1
pre-tracking 1
Related categories
⚡
DEV INTEL
Tools & Severity
⚙ Fix effort: Medium
⚡ Quick Fix
Add a version INT NOT NULL DEFAULT 0 column, increment in every UPDATE, check rowCount() === 1 after execute()
📦 Applies To
web
cli
🔗 Prerequisites
🔍 Detection Hints
UPDATE without version check on shared mutable rows
Auto-detectable:
✗ No
⚠ Related Problems
🤖 AI Agent
Confidence: Low
False Positives: High
✗ Manual fix
Fix: High
Context: File
Tests: Update