Worked examples — Optimistic vs pessimistic concurrency control
This page is the exhaustive case-list for the parent topic. The parent gave you the idea (lock-first vs work-then-check) and the break-even inequality. Here we hit every corner: low/high contention, the exact break-even point, degenerate inputs (, ), write skew that single-row checks miss, deadlock, and a real-world flash sale. Every number asserted on this page is re-checked by machine code in the hidden Verify block that ships with this note (the same numbers you see in each "Verify:" line).
Before we start, one reminder of the two quantities everything hinges on — and where the optimistic formula comes from, since the parent stated it but this page leans on it in every cell:
Everything below is a specific point on the -axis or a specific anomaly shape. Let's name all the cells first.
The scenario matrix
Think of the whole topic as a grid. One axis is how likely a conflict is (the number ). The other axis is what shape the conflict has (same row? different rows? no real conflict at all?). Every worked example below is tagged with the cell it lands in.
| Cell | Class | Concrete input | What it stresses |
|---|---|---|---|
| A | (no conflict ever) | read-only report, | degenerate low end: does the formula stay finite? |
| B | (low contention) | , , | OCC should win comfortably |
| C | (exact break-even) | , , | the tie — both cost the same |
| D | (high contention) | , , | PCC should win |
| E | (limiting / degenerate high end) | flash sale, | OCC cost (livelock) |
| F | conflict shape = same row | lost update, two ATMs | row lock / version check catches it |
| G | conflict shape = different rows | write skew, two doctors | single-row check misses it |
| H | deadlock (PCC's own failure mode) | T1↔T2 cross-lock | why OCC never deadlocks |
| I | real-world word problem | inventory microservice | pick a strategy from a story |
| J | exam twist | "is MVCC optimistic?" | trap that mixes storage with strategy |
We now walk every cell. Cells A–E are points along the contention axis; F–J are conflict-shape and reasoning cells.
Reading the figure below (in words): the horizontal axis is from to . The flat blue line at height is the pessimistic cost ms — flat because it never depends on . The rising orange curve is the optimistic cost — it starts at on the far left, climbs slowly, and shoots up toward the right edge as nears . The two curves cross at the dashed gray vertical line . Left of the crossing (green band) OCC is cheaper; right of it (red band) PCC is cheaper. Each red dot is labelled with its cell letter (A–E) right on the plot, so the "far-left dot" / "high dot" pointers in the text below always match a labelled point in the graphic.

Worked examples
Cell A — : no conflict ever
A reporting job runs at 3 AM. Nothing else writes. So . Lock overhead ms, retry ms. Compute the expected cost of each strategy.
Forecast: OCC should be essentially free here — guess its cost before reading on.
- Optimistic cost. Plug into ms. Why this step? With the retry term vanishes — you never fail validation, so you never pay .
- Pessimistic cost. ms, always. Why this step? Pessimistic pays lock overhead unconditionally, even when nobody contends — that is its defining weakness.
- Decision. OCC wins.
Verify: at is , finite and well-defined — no division blow-up. Units: ms compared to ms. ✅ This is the red dot labelled A at the far left of the figure, sitting on the axis at height .
Cell B — : low contention
, ms, ms. First find the break-even , then decide.
Forecast: guess whether is above or below the tipping point.
- Break-even. . Why this step? is the single threshold separating "OCC wins" from "PCC wins"; compute it once and compare.
- Compare to . , so we expect OCC to win. Why this step? The inequality is exactly the OCC-wins condition.
- Confirm with cost. ms vs ms. Why this step? Comparing the actual expected costs double-checks the threshold logic.
Verify: ✅ OCC is ~12× cheaper. Units ms vs ms. This is the red dot labelled B hugging the bottom of the figure, well inside the green band.
Cell C — : exact break-even
, ms, ms. What happens at the knife-edge?
Forecast: guess the ratio of the two costs.
- Optimistic cost. ms. Why this step? This is the value was defined to produce — plug it back to see the equality.
- Pessimistic cost. ms.
- Decision. Costs are equal () — a genuine tie; pick based on secondary concerns (deadlock risk, code simplicity). Why this step? At break-even the formula gives no preference; the tie-breaker is engineering, not arithmetic.
Verify: solving for the given gives exactly, and both sides equal . ✅ This is the red dot labelled C, exactly where the orange curve crosses the blue line, on the dashed gray vertical.
Cell D — : high contention
, ms, ms.
Forecast: half of all transactions retry at least once — guess who wins.
- Optimistic cost. ms. Why this step? at equals : on average one full retry per transaction, so you pay once.
- Pessimistic cost. ms.
- Decision. PCC wins by 4×. Why this step? Above the retry term overtakes the fixed lock cost.
Verify: ✅ Consistent with . Units ms. This is the red dot labelled D, high in the red band, above the blue line.
Cell E — : the limiting degenerate case
10,000 buyers hit the same inventory row per second. Effectively (but never exactly — recall we excluded as the wall the formula runs into). What does OCC cost in the limit?
Forecast: guess the limit of as .
- Take the limit. . Why this step? The denominator shrinks to , so the expected number of retries explodes.
- What "explodes" looks like: livelock.
Definition Livelock A livelock is when transactions are busy but never finishing: each one keeps re-reading, re-computing, and failing validation because someone else committed first — like two people in a hallway stepping side-to-side forever, always moving, never passing. (Contrast a deadlock, where everyone is frozen and not moving.) Here every buyer's
WHERE version = ...matches 0 rows over and over, so each restarts endlessly. - Numeric feel at . ms. Why this step? Showing makes the "explosion" concrete: ~99 retries on average.
- Decision. ms use pessimistic (a single queue / atomic decrement per the Compare-and-Swap (CAS) pattern or a serialized lock).
Verify: ✅ and the closed form diverges as . This is the red dot labelled E at the far right, where the orange curve rockets upward toward the top of the plot.
Cell F — conflict shape = same row (lost update)
Account balance = \100= 7$30= $40$. Trace both strategies.
Forecast: without any control both write \70$40$.
- No control (the bug). Both read , both compute , both write . One update is lost. Why this step? Establishes the anomaly we must defeat — a same-row read-modify-write race.
- PCC fix. ATM-1 takes
SELECT ... FOR UPDATE(X-lock), writes , commits. ATM-2 was blocked; it now reads the fresh , computes , writes . Why this step? The exclusive lock serializes the two — Two-Phase Locking (2PL) guarantees this order. - OCC fix. ATM-2 runs
UPDATE ... SET balance=70, version=8 WHERE id=1 AND version=7. But ATM-1 already bumped version to , so ATM-2'sWHERE version=7matches 0 rows → abort, re-read , recompute , write withversion=9. Why this step? The version mismatch is the validation failure — a conditionalWHEREacts as the check.
Verify: correct final balance ; both fixes land on , not . ✅ Version sequence is strictly increasing.
Cell G — conflict shape = different rows (write skew)
Invariant: at least 1 doctor on call. Currently Alice and Bob are both on call (2 rows, on_call = true). Each independently requests to go off-shift. Each transaction reads "count = 2 ≥ 1, safe" then sets its own row to false. Result: 0 on call — invariant broken.
Forecast: guess why a plain per-row version check does NOT save you here.
- Why plain OCC / row locks fail. Alice writes row Alice; Bob writes row Bob. They touch different rows, so neither version check nor row X-lock collides. Why this step? This is exactly the Write Skew and Phantoms pattern — the conflict is over a condition across rows, not a single cell.
- PCC fix. Use predicate/range locking: lock the set "doctors where on_call = true". The second transaction's read-lock on that predicate blocks (or is caught) → serialized → one doctor stays on. Why this step? The invariant lives on a predicate, so the lock must too.
- OCC fix (serializable snapshot isolation). Validation tracks the read set (the predicate "count on call"), not just written rows. If Bob's write changed a row Alice read for her decision, one commit is aborted. Why this step? SSI layers optimistic validation over MVCC and covers read-write dependencies — see Serializability and Isolation Levels.
Verify: with the fix, exactly one of {Alice, Bob} commits their off-shift; final on-call count (invariant holds). ✅ Without it, count (violated).
Cell H — deadlock: PCC's own failure mode
T1 locks row then wants . T2 locks row then wants . Neither releases (both in the growing phase of 2PL). Trace what OCC would do instead.
Forecast: guess how many transactions make progress under pure 2PL here.
- PCC outcome. T1 holds , waits on ; T2 holds , waits on . Circular wait → deadlock. Progress until a detector/timeout aborts one. Why this step? Under 2PL you may not acquire a new lock after releasing one, so neither transaction can voluntarily back off and release mid-way — a wait-for cycle forms and freezes both.
- PCC resolution. A deadlock detector finds the cycle in the wait-for graph, aborts (say) T2. T1 then finishes; T2 retries from scratch. Net: 1 abort forced by the lock mechanism, not by validation. Why this step? Shows PCC still sometimes retries — but its retries are triggered by deadlock detection, a cost invisible in the simple model.
- OCC outcome — and why it makes progress. OCC holds no locks during the read/compute phase, so no transaction ever waits on another's lock → the wait-for graph has no edges → no cycle → 0 deadlocks. Validation runs at the instant of commit (the write phase) and is atomic: whichever transaction reaches its commit-time version check first wins and commits; the loser sees a bumped version, aborts, and retries. Why this step? This is the precise contrast — PCC can freeze in a cyclic wait; OCC cannot, because nobody blocks on a held lock, so there is nothing to form a cycle out of.
- Why OCC still guarantees forward progress. At every commit round some transaction reaches the atomic check first, so some transaction always commits. The loser never blocks the winner — it simply re-runs. So the system as a whole always advances (no permanent freeze), even though an individual unlucky transaction may retry. Why this step? Distinguishes "no deadlock" (structural) from "makes progress" (a live winner exists each round) — both hold for OCC; only the second can fail under extreme contention as livelock (Cell E).
Verify: OCC holds no locks during the read phase, so its wait-for graph has no edges → no cycle → 0 deadlocks. Under PCC exactly 1 of the 2 transactions is aborted to break the cycle. ✅
Cell I — real-world word problem
An e-commerce catalog has 1,000,000 SKUs. Orders arrive uniformly across all SKUs at 5,000 orders/sec. Two orders touch the same SKU only rarely. The measured conflict rate is . Overheads ms, ms. Which control?
Forecast: with a million spread-out SKUs, guess whether conflicts clear the threshold.
- Break-even. . Why this step? Establishes the tipping point to compare the measured against.
- Compare. , so OCC is favoured. Why this step? Load spread over a million rows means tiny — the exact situation OCC was built for.
- Cost check. ms vs ms. Why this step? Confirms OCC is ~250× cheaper; a per-SKU version column suffices, no lock overhead.
- Decision. Optimistic (version-column check per SKU). Why this step? Both the threshold test and the raw cost agree — a robust pick.
Verify: ✅ (Contrast with Cell E's flash sale on one SKU where flips the same shop to pessimistic — same business, opposite answer, driven purely by .)
Cell J — exam twist (the trap)
Forecast: guess before reading — versions feel optimistic.
- Separate the two axes. MVCC is a storage technique (keep multiple row versions so readers see a consistent snapshot). Optimistic/pessimistic is a strategy (when to check for conflicts). These are two independent choices. Why this step? The trap conflates how data is stored with how conflicts are resolved.
- Counter-examples both ways. PostgreSQL uses MVCC with mostly-pessimistic writer locks. Serializable Snapshot Isolation uses MVCC with optimistic validation on top. Why this step? One storage layer powers both strategies → the claim can't be an identity.
- Answer. False. MVCC ≠ optimistic; it's orthogonal. This still delivers the I (isolation) of ACID properties, but by either route. Why this step? Names the actual relationship (orthogonal, not equal) and ties it back to the isolation goal.
Verify: logical check — if MVCC implied optimistic, then no MVCC system could use pessimistic locks; but Postgres (MVCC + row X-locks) is a working counterexample, so the implication is false. ✅
Recall Which cell is which — self-test
, cost of OCC ::: ms (retry term vanishes; formula stays finite). Why is excluded up front ::: because has a term that diverges to infinity there — it's a wall, not a value. What shared cost cancels from the comparison ::: the base read/compute/write cost , paid once under both strategies. Break-even with ::: . OCC cost at exact break-even ::: ms — a tie with PCC. OCC cost at (hot counter) ::: ms — PCC wins. Limit of OCC cost as ::: (livelock). Anomaly where writes hit different rows ::: write skew (needs predicate/serializable, not row check). Which strategy can deadlock ::: pessimistic (2PL); OCC aborts instead, never deadlocks. When does OCC validation run ::: at the atomic commit instant (write phase); the first to reach it wins, guaranteeing progress. Cell I decision (million SKUs, p=0.001) ::: optimistic — conflicts far below threshold. Is MVCC the same as optimistic ::: No — MVCC is storage, orthogonal to the strategy.