Conceptsβ€’Jun 2026β€’4 min read

Pessimistic Concurrency Control vs Transactions: Stop Confusing the Lock for the Contract

Pessimistic concurrency control is a strategy for handling contention; a transaction is the atomicity-and-isolation contract that makes contention safe to handle. They live on different layers, and pretending they compete is how people ship broken data.

The short answer

Transactions over Pessimistic Concurrency Control for most cases. This is a layer confusion, not a real fight.

  • Pick Pessimistic Concurrency Control if contention is high, conflicts are expensive to retry, and you can tolerate blocking and reduced throughput β€” think inventory decrements or seat booking under heavy write collision
  • Pick Transactions Stop Confusing The Lock For The Contract if basically always β€” you need a transaction to get atomicity and isolation regardless. Then choose pessimistic OR optimistic control inside it
  • Also consider: For low-contention workloads, keep the transaction but use optimistic concurrency (version columns / SELECT ... FOR UPDATE only where needed). Reserve pessimistic locking for genuine hotspots.

β€” Nice Pick, opinionated tool recommendations

They are not the same kind of thing

This comparison is a category error people make constantly, so let me end it. A transaction is a correctness contract: a group of operations that commit all-or-nothing, with an isolation guarantee about what concurrent readers and writers can see. Pessimistic concurrency control is a strategy for resolving write contention β€” grab a lock before you touch the row (SELECT ... FOR UPDATE), make everyone else wait. The strategy runs inside the contract. You wrap a transaction around the locked rows because without it, your lock protects nothing on rollback. Asking 'pessimistic locking or transactions?' is like asking 'seatbelt or driving?'. The honest framing is: you always want a transaction, and pessimistic locking is one of two ways to handle conflicts within it β€” the other being optimistic. Pick that, not whether transactions exist.

When pessimistic locking actually earns its keep

Pessimistic control wins exactly one fight: high-contention hotspots where conflicts are frequent AND retrying is expensive or unfair. Decrementing the last unit of inventory, allocating a unique seat, transferring balance between two hot accounts β€” here optimistic 'retry on version mismatch' degenerates into a livelock of doomed retries, and the row is genuinely worth serializing access to. SELECT ... FOR UPDATE makes the second writer wait instead of failing. The cost is brutal and real: you hold locks across the transaction, so throughput collapses, lock ordering bugs become deadlocks, and a slow client holding a row lock stalls everyone behind it. Pessimistic locking trades concurrency for determinism. That trade is correct only when collisions are the common case. If conflicts are rare, you are punishing every request to soothe a problem that almost never happens. Measure contention before reaching for it.

Transactions are non-negotiable; the control mode is the real choice

You don't get to skip the transaction. Without atomicity, a partial write on failure leaves your data lying. Without isolation, concurrent transactions read each other's uncommitted garbage β€” dirty reads, lost updates, write skew. Every serious database gives you transactions; the interesting knob is isolation level (Read Committed, Repeatable Read, Serializable) and whether you resolve conflicts pessimistically or optimistically. Optimistic control β€” version columns, compare-and-swap, Serializable-with-retry β€” assumes conflicts are rare, does no blocking, and aborts the loser to retry. It is the right default for the overwhelming majority of web workloads, where two requests rarely touch the same row in the same instant. Pessimistic is the specialist you call in for the 5% of hot rows. So the decision tree is: always wrap a transaction, default to optimistic, escalate specific hotspots to pessimistic. Anyone who tells you to choose 'transactions vs locking' has skipped the only part that matters.

The mistakes I see in production

Three failure modes, all from misreading this comparison. One: people add SELECT ... FOR UPDATE everywhere 'to be safe,' serialize their whole workload, and then wonder why p99 latency exploded under load β€” they bought pessimistic costs on cold rows that never collide. Two: people use optimistic version checks but forget the retry loop, so a version mismatch surfaces as a user-facing 500 instead of a transparent redo. Three β€” the worst β€” people hold a pessimistic lock and then make a network call (payment APIΡ–Π³ΠΎΡ€, third-party check) inside the transaction, parking a row lock for 800ms and deadlocking the system the first time that API hiccups. Never do I/O to an external service while holding a lock. Keep transactions short, keep external calls outside them, and let the contention rate, not your anxiety, decide pessimistic versus optimistic. The transaction is mandatory; the locking strategy is a measurement, not a vibe.

Quick Comparison

FactorPessimistic Concurrency ControlTransactions Stop Confusing The Lock For The Contract
What it isA contention-handling strategy (block via locks)An atomicity + isolation correctness contract
Can you skip it?Yes β€” optimistic control is a valid alternativeNo β€” required for correct concurrent writes
Best fitHigh-contention hotspots, expensive retriesEvery workload that mutates shared data
Throughput costHigh β€” blocking, deadlock risk, serialized hot rowsLow overhead; cost depends on isolation level chosen
Failure mode when misusedLock held across I/O β†’ deadlocks, latency spikesWrong isolation β†’ write skew / lost updates

The Verdict

Use Pessimistic Concurrency Control if: Contention is high, conflicts are expensive to retry, and you can tolerate blocking and reduced throughput β€” think inventory decrements or seat booking under heavy write collision.

Use Transactions Stop Confusing The Lock For The Contract if: Basically always β€” you need a transaction to get atomicity and isolation regardless. Then choose pessimistic OR optimistic control inside it.

Consider: For low-contention workloads, keep the transaction but use optimistic concurrency (version columns / SELECT ... FOR UPDATE only where needed). Reserve pessimistic locking for genuine hotspots.

🧊
The Bottom Line
Transactions wins

This is a layer confusion, not a real fight. A transaction is the correctness primitive you cannot do without; pessimistic locking is one of several tactics for resolving contention inside it. You can run transactions with optimistic control instead, but you cannot run pessimistic locking without a transaction to scope the locks. The foundation wins over the technique.

Related Comparisons

Disagree? nice@nicepick.dev