Skip to content
About
Main content

Optimistic concurrency control (OCC)

Below which conflict probability does optimism win, and how does it fail above it?

The constraint is economic

If conflicts are rare, a is a cost paid on every operation to protect against something that almost never happens. Put numbers on it.

  • Pessimistic (locking) cost ≈ k × c_lock per transaction. With k = 10 items and c_lock ≈ 150 ns: 10 × 150 ns = 1.5 µs.
  • Optimistic cost ≈ p_conflict × (the transaction's entire work), because a conflicting transaction must redo everything. For 100 µs of work, the two costs are equal when p × 100 µs = 1.5 µs, so p ≈ 1.5 / 100 = 1.5%.
items per transaction
cost of one lock acquisition
chance a transaction must redo its work
lockingoptimistic
Data table
Cost per transaction by conflict probability (k = 10, 150 ns per lock, 100 µs of work)
Conflict probability pseriesCost per transaction
0locking1.5 µs
0locking1.5 µs
0locking1.5 µs
0locking1.5 µs
0locking1.5 µs
0locking1.5 µs
0locking1.5 µs
0locking1.5 µs
0locking1.5 µs
0locking1.5 µs
0locking1.5 µs
0locking1.5 µs
0locking1.5 µs
0locking1.5 µs
0locking1.5 µs
0locking1.5 µs
0locking1.5 µs
0.001locking1.5 µs
0.001locking1.5 µs
0.001locking1.5 µs
0.001locking1.5 µs
0.001locking1.5 µs
0.001locking1.5 µs
0.001locking1.5 µs
0.001locking1.5 µs
0.001locking1.5 µs
0.001locking1.5 µs
0.001locking1.5 µs
0.001locking1.5 µs
0.002locking1.5 µs
0.002locking1.5 µs
0.002locking1.5 µs
0.002locking1.5 µs
0.002locking1.5 µs
0.003locking1.5 µs
0.003locking1.5 µs
0.003locking1.5 µs
0.003locking1.5 µs
0.004locking1.5 µs
0.004locking1.5 µs
0.004locking1.5 µs
0.005locking1.5 µs
0.005locking1.5 µs
0.006locking1.5 µs
0.007locking1.5 µs
0.007locking1.5 µs
0.008locking1.5 µs
0.009locking1.5 µs
0.01locking1.5 µs
0.011locking1.5 µs
0.012locking1.5 µs
0.013locking1.5 µs
0.014locking1.5 µs
0.015locking1.5 µs
0.017locking1.5 µs
0.019locking1.5 µs
0.021locking1.5 µs
0.023locking1.5 µs
0.025locking1.5 µs
0.027locking1.5 µs
0.03locking1.5 µs
0.033locking1.5 µs
0.036locking1.5 µs
0.04locking1.5 µs
0.044locking1.5 µs
0.048locking1.5 µs
0.053locking1.5 µs
0.058locking1.5 µs
0.064locking1.5 µs
0.07locking1.5 µs
0.077locking1.5 µs
0.085locking1.5 µs
0.094locking1.5 µs
0.103locking1.5 µs
0.113locking1.5 µs
0.124locking1.5 µs
0.137locking1.5 µs
0.15locking1.5 µs
0.165locking1.5 µs
0.182locking1.5 µs
0.2locking1.5 µs
0optimistic0.01 µs
0optimistic0.011 µs
0optimistic0.012 µs
0optimistic0.013 µs
0optimistic0.015 µs
0optimistic0.016 µs
0optimistic0.018 µs
0optimistic0.019 µs
0optimistic0.021 µs
0optimistic0.024 µs
0optimistic0.026 µs
0optimistic0.028 µs
0optimistic0.031 µs
0optimistic0.034 µs
0optimistic0.038 µs
0optimistic0.042 µs
0optimistic0.046 µs
0.001optimistic0.05 µs
0.001optimistic0.055 µs
0.001optimistic0.061 µs
0.001optimistic0.067 µs
0.001optimistic0.074 µs
0.001optimistic0.081 µs
0.001optimistic0.089 µs
0.001optimistic0.098 µs
0.001optimistic0.108 µs
0.001optimistic0.118 µs
0.001optimistic0.13 µs
0.001optimistic0.143 µs
0.002optimistic0.157 µs
0.002optimistic0.173 µs
0.002optimistic0.19 µs
0.002optimistic0.209 µs
0.002optimistic0.23 µs
0.003optimistic0.253 µs
0.003optimistic0.278 µs
0.003optimistic0.306 µs
0.003optimistic0.336 µs
0.004optimistic0.37 µs
0.004optimistic0.407 µs
0.004optimistic0.447 µs
0.005optimistic0.492 µs
0.005optimistic0.541 µs
0.006optimistic0.595 µs
0.007optimistic0.654 µs
0.007optimistic0.719 µs
0.008optimistic0.791 µs
0.009optimistic0.87 µs
0.01optimistic0.956 µs
0.011optimistic1.05 µs
0.012optimistic1.16 µs
0.013optimistic1.27 µs
0.014optimistic1.4 µs
0.015optimistic1.54 µs
0.017optimistic1.69 µs
0.019optimistic1.86 µs
0.021optimistic2.05 µs
0.023optimistic2.25 µs
0.025optimistic2.47 µs
0.027optimistic2.72 µs
0.03optimistic2.99 µs
0.033optimistic3.29 µs
0.036optimistic3.62 µs
0.04optimistic3.98 µs
0.044optimistic4.37 µs
0.048optimistic4.81 µs
0.053optimistic5.29 µs
0.058optimistic5.82 µs
0.064optimistic6.4 µs
0.07optimistic7.03 µs
0.077optimistic7.73 µs
0.085optimistic8.5 µs
0.094optimistic9.35 µs
0.103optimistic10.3 µs
0.113optimistic11.3 µs
0.124optimistic12.4 µs
0.137optimistic13.7 µs
0.15optimistic15 µs
0.165optimistic16.5 µs
0.182optimistic18.2 µs
0.2optimistic20 µs

Three phases

  1. Read into a private workspace (nothing is visible to others yet).
  2. Validate: did anything I read change since I read it?
  3. Write: make the changes visible.

There are two directions for validation:

  • Backward validation checks my read set against the write sets of transactions that committed since I started.
  • Forward validation checks my write set against the read sets of transactions that are still running. Its useful property: you can choose to the other transaction. That is the hook by which priority and starvation avoidance are implemented.

The structural cost: validation is itself a critical section

Check-then-commit must be atomic with respect to other validations. Otherwise two transactions could each validate against a state that neither ends up in. So the scheme that abolished locking has created one global , and Amdahl's Law (the speedup limit set by the serial part of a job) now sets its ceiling at the reciprocal of that critical section's length.

Modern designs spend most of their engineering here. Silo (a research in-memory database) replaces the global section with per-record transaction IDs plus epoch-based . This is a fixed per-commit cost deriving a natural batch size, and the batch is an epoch (a short period of time, for example 40 ms, in which are grouped).

How OCC fails

The failure mode is different from 's.

  • 2PL degrades into waiting: utilization drops, queues grow, and the system is visibly stuck.
  • OCC degrades into burning: every core is at 100%, (useful completed work) is near zero, because everyone aborts everyone and nobody finishes.

An aborted transaction also used a concurrency slot for its whole duration, so by it consumed real capacity to produce nothing. If you diagnose this in production, the tell is high CPU with a falling commit rate. goodput, not throughput.

OCC starves the large

A long transaction's conflict window is proportional to its length, so under contention it will essentially never commit, while 2PL's first-come-first-served wait queues starve nobody. The standard fix is to fall back to pessimistic execution after N aborts. That is an admission that the scheme has one workload it likes and needs an escape for the other.

Optimism trades a certain small cost on every transaction for an uncertain total loss on a few; the crossover is the conflict probability, and past it the failure is silent because the CPUs stay busy.

A code sketch: optimistic update with a version column

Application code can use even on a database that locks. Keep a version number in the row and make the update conditional on it:

-- 1. Read, remember the version.
SELECT balance, version FROM wallets WHERE owner = 'anna';   -- balance=100, version=7

-- 2. Compute in the application, then write only if nobody changed the row.
UPDATE wallets
   SET balance = 70, version = version + 1
 WHERE owner = 'anna' AND version = 7;
-- If this reports 0 rows updated, someone else got there first:
-- re-read and try again (the retry must include the read).