---
topic: dev-practices
author: Crashtech Editorial
published: Nov 14, 2026 · read: 3 min
---

Pessimistic vs Optimistic Locking: Choosing by How Often Your Writes Collide

Lock first or check at commit? Version columns, SELECT FOR UPDATE, retry logic and the contention numbers that decide which strategy fits.

– –

Two users open the same record, both edit, both save. Without a rule, the second save silently erases the first. The two classic rules differ in when they pay: before the work, with a lock, or after, with a check.

The lost update

t1  Alice reads balance = 100
t2  Bob   reads balance = 100
t3  Alice writes 100 + 50 = 150
t4  Bob   writes 100 - 30 =  70   <- Alice's deposit is gone

Both transactions were valid in isolation. The failure is in the gap between reading and writing.

Pessimistic: lock first

Take the lock when you read, hold it until you commit.

BEGIN;
SELECT balance FROM accounts WHERE id = 7 FOR UPDATE;   -- row locked
UPDATE accounts SET balance = balance + 50 WHERE id = 7;
COMMIT;                                                  -- lock released

Bob’s SELECT ... FOR UPDATE blocks until Alice commits, then sees 150. No lost update, no retry logic.

Strengths: predictable, each transaction succeeds once, safe when work between read and write has side effects. Costs: waiting, reduced throughput, possible deadlocks, and long locks if code does slow work inside the transaction.

Optimistic: check at the end

No lock. Add a version column and make the write conditional.

-- read
SELECT balance, version FROM accounts WHERE id = 7;     -- 100, version 4

-- write, only if nobody changed it
UPDATE accounts
SET balance = 150, version = version + 1
WHERE id = 7 AND version = 4;
-- rows affected = 0  ->  conflict: reload and retry, or tell the user

Many ORMs implement this with a version or timestamp annotation. In HTTP APIs, the same idea is the ETag plus If-Match header: update only if the resource is still the version you saw.

Strengths: no waiting, great read concurrency, scales well when conflicts are rare, works across requests and user think-time without holding locks. Costs: conflicts surface as errors you must handle, wasted work on retry, and poor behaviour on hot rows.

Never hold a database lock across user think time

A pessimistic lock held while a person reads a form and decides blocks everyone else for minutes, and survives only as long as the connection does. For interactive editing across requests, use optimistic checks with versions, or an application-level lease with an expiry.

Choosing

QuestionOptimisticPessimistic
How often do writers collide on the same row?RarelyOften
Cost of a retry?Cheap, work is repeatableExpensive, or has side effects
Read-to-write gap?Long, user in the loopShort, inside one transaction
Throughput priority?HighCorrectness over speed
Typical fitProfiles, documents, settings, admin editsInventory of last items, seat booking, ledgers, job claiming
Advertisement

Practices that apply to both

  1. Keep the critical section short

    Do external calls, slow computation and user interaction outside the transaction. The shorter the window, the fewer collisions and waits.

  2. Lock at the finest useful granularity

    Rows, not tables. Use indexes so the engine locks only the rows you mean.

  3. Order lock acquisition consistently

    When taking locks on several rows, always take them in the same order, for example by ascending id, to avoid deadlocks.

  4. Retry with limits and backoff

    For optimistic conflicts and deadlock victims, retry a bounded number of times with jitter, then fail clearly. Make the operation idempotent so a retry is safe.

  5. Measure conflict rates

    Count version-check failures and lock wait times. A rising retry rate on optimistic paths is the signal to redesign or switch.

Third options worth knowing

  • Atomic statements: UPDATE stock SET qty = qty - 1 WHERE sku = ? AND qty > 0 needs neither strategy for simple counters.
  • SELECT ... FOR UPDATE SKIP LOCKED: lets workers claim queue rows without blocking each other.
  • Unique constraints: let the database reject the second insert instead of checking first.

Do

Default to optimistic for user-edited records and independent writes, use pessimistic or atomic statements for hot counters and scarce resources, and always handle the failure path in code and in the UI.

Don't

Ignore the zero-rows-updated case. An optimistic check you do not inspect is just a silent lost update with extra steps.

Takeaway

Both strategies prevent lost updates, and they pay at different times. Optimistic locking pays rarely but sharply, in a retry. Pessimistic locking pays continuously, in waiting. Estimate how often your writers collide and what a retry costs, then choose, and keep every transaction short either way.

Advertisement

Frequently asked questions

What is the difference between optimistic and pessimistic locking?

Pessimistic locking takes a lock before reading data you intend to change, so others wait. Optimistic locking takes no lock, reads freely, and at write time checks that nobody changed the data since you read it, typically with a version number, retrying or failing if they did.

How does optimistic locking work with a version column?

Each row has a version. You read it, then write with a condition: update the row only where the id and version still match what you read, and increment the version. If zero rows were updated, someone else got there first, so you reload and retry or report a conflict.

When is pessimistic locking the better choice?

When conflicts are frequent or retries are costly or unsafe: scarce inventory with many buyers, seat reservations, or work that has side effects before the final write. Locking serialises access so each transaction succeeds once, at the cost of waiting and potential deadlocks.

Can optimistic locking cause starvation?

Yes. Under heavy contention on the same row, many transactions read the same version and only one wins each round, so others keep retrying and wasting work. If you see high retry rates, either redesign to reduce contention or switch the hot path to pessimistic locking.

Sources & further reading

/* Comments */