Pessimistic Concurrency Control vs Transactions
Pessimistic concurrency control is a locking strategy; a transaction is the atomic unit it operates inside. Comparing them is a category error — but if you're forced to choose what to reach for, here's the decisive read.
The short answer
Transactions over Pessimistic Concurrency Control for most cases. A transaction is the foundational guarantee — atomicity, isolation, durability — that every correct write depends on.
- Pick Pessimistic Concurrency Control if have genuine high-contention hotspots — inventory decrements on a single SKU, ledger balances, seat booking — where retry storms under optimistic control would be worse than the wait, and you can hold short, well-ordered locks
- Pick Transactions if always. Every write path needs a transaction. Default to it, wrap your reads-then-writes, and only reach for pessimistic locking on the specific rows that actually contend
- Also consider: MVCC with optimistic concurrency (or SELECT ... FOR UPDATE only on proven hotspots) covers ~95% of real workloads. Reach for blanket pessimistic locking last, not first.
— Nice Pick, opinionated tool recommendations
They're not the same kind of thing
Let's kill the framing first. A transaction is a contract: a group of operations that commit all-or-nothing, with isolation from concurrent work. Pessimistic concurrency control (PCC) is a tactic for enforcing isolation — you grab a lock before touching data so nobody else can, and you hold it until commit. PCC lives inside a transaction; it is not an alternative to one. Asking 'PCC or transactions?' is like asking 'seatbelts or cars?'. That said, the question people actually mean is real: should I lock pessimistically, or lean on transactions plus optimistic checks? That's answerable, and the answer is mostly 'don't grab the lock.' Most engineers reach for SELECT ... FOR UPDATE out of fear, not measurement, and pay for it in throughput they never had to spend. Know which layer you're arguing about before you argue.
Where pessimistic locking earns its keep
PCC isn't a sin — it's a specialized tool people overuse. It shines exactly where contention is real and conflicts are expensive to redo. Decrementing the last few units of a hot inventory SKU on Black Friday: optimistic retry-and-fail becomes a thundering herd of aborts, and a short FOR UPDATE lock that serializes the hotspot is genuinely faster end-to-end. Same for financial ledgers where you must read a balance, validate, then write, and a failed retry means re-running expensive business logic. PCC gives you a clean mental model: nobody else touches this row until I'm done. The cost is throughput and deadlock risk. Hold locks too long, acquire them in inconsistent order, or lock too broadly, and you've traded conflicts for stalls and 40001 deadlock-victim errors. Used surgically — short, ordered, narrow — it's correct and predictable. Used by default, it's a self-inflicted bottleneck.
Why transactions win the actual decision
Strip away the false binary and the practical guidance is blunt: wrap everything in a transaction, and default your concurrency strategy to optimistic, not pessimistic. Modern databases run MVCC — readers don't block writers, writers don't block readers — so most 'concurrency problems' evaporate under snapshot isolation without a single explicit lock. Add a version column or a WHERE-clause guard, retry on conflict, and you handle real contention with zero lock-hold time. Pessimistic locking should be the exception you justify per-hotspot with a profiler, not the posture you adopt because a Stack Overflow answer scared you. The transaction is non-negotiable infrastructure; PCC is an optimization you mostly shouldn't apply. Teams that lead with FOR UPDATE everywhere build systems that fall over at the exact moment traffic spikes — the deadlocks and lock waits show up precisely when you can least afford them. Transactions first. Optimistic by default. Pessimistic only where you've proven it pays.
The honest scorecard
Here's the part nobody says out loud: for the overwhelming majority of CRUD applications, you will never need to type FOR UPDATE once. Snapshot isolation plus a transaction handles your order forms, your user profiles, your settings tables, your dashboards. The cases that genuinely demand pessimistic locking are narrow and identifiable — single-row hotspots with expensive validation logic and high write contention — and you'll know them because the profiler screams about retry rates, not because you guessed. Reach for pessimistic locking the way you reach for a fire extinguisher: deliberately, at a specific fire, not preemptively in every room. The failure mode of over-using transactions is essentially nothing — you can't have too many. The failure mode of over-using pessimistic locks is a deadlock-ridden system that throughput-collapses under the exact load you built it to survive. That asymmetry is the whole verdict. One is the floor you stand on; the other is a power tool that bites the careless.
Quick Comparison
| Factor | Pessimistic Concurrency Control | Transactions |
|---|---|---|
| What it actually is | A locking tactic — grab a lock before touching data, hold to commit | The atomic, isolated, durable unit of work itself |
| Default suitability | Specialized — right only on proven high-contention hotspots | Universal — every correct write path needs one |
| Throughput under load | Lock waits and deadlocks spike exactly when traffic spikes | Neutral — MVCC lets readers and writers not block each other |
| Hotspot contention (last-item inventory, ledger) | Serializes cleanly, avoids retry storms on expensive redos | Needs optimistic retries that can thunder under heavy conflict |
| Failure mode | Deadlocks, lock-order bugs, stalls from over-broad locks | Serialization conflicts you retry — bounded and predictable |
The Verdict
Use Pessimistic Concurrency Control if: You have genuine high-contention hotspots — inventory decrements on a single SKU, ledger balances, seat booking — where retry storms under optimistic control would be worse than the wait, and you can hold short, well-ordered locks.
Use Transactions if: Always. Every write path needs a transaction. Default to it, wrap your reads-then-writes, and only reach for pessimistic locking on the specific rows that actually contend.
Consider: MVCC with optimistic concurrency (or SELECT ... FOR UPDATE only on proven hotspots) covers ~95% of real workloads. Reach for blanket pessimistic locking last, not first.
A transaction is the foundational guarantee — atomicity, isolation, durability — that every correct write depends on. Pessimistic concurrency control is one locking flavor you optionally layer on top of it, and usually the wrong one. You can build a correct system with transactions and optimistic checks and never take a pessimistic lock. You cannot build a correct system without transactions. The unit wins over the tactic.
Related Comparisons
Disagree? nice@nicepick.dev