ReviseAlgo Logo

Transactions & Concurrency

Optimistic vs Pessimistic Locking — Preventing Data Corruption

Using @Version annotations vs SELECT FOR UPDATE database locks.

Last Updated: June 14, 2026 15 min read

Introduction

When multiple users attempt to read and update the same database record concurrently, data conflicts can arise. To prevent the **Lost Update anomaly** (where User A's changes are silently overwritten by User B), applications must enforce concurrency control using **Optimistic Locking** or **Pessimistic Locking**.

Why It Matters

Without concurrency locks, systems suffer from critical data bugs. For example, two customers checking out a seat in a ticket booking system at the same time could both be allocated the same seat, or account balances could be decremented incorrectly, corrupting ledger integrity.

Real-World Analogy

Think of locks like reserving seats in a restaurant:
Optimistic Locking (Google Docs style): Two parties read the menu. They write down their order. When submitting, the kitchen checks if the table has ordered already. If another party submitted an order for that table first, the system tells you to refresh and order again.
Pessimistic Locking (Library book style): You claim the table physical menu card. No one else can look at or order for that table until you give the menu card back to the waiter.

Detailed Mechanics

1. Optimistic Locking

Optimistic locking assumes conflicts are rare. It uses a @Version attribute (a number or timestamp) on the entity:

If another transaction committed an update while this transaction was running, the database row version would already be 2. The update query affects 0 rows. Hibernate detects this and throws an OptimisticLockException (wrapped as ObjectOptimisticLockingFailureException by Spring), prompting a retry.

2. Pessimistic Locking

Pessimistic locking assumes conflicts are highly likely. It locks the database record at the database engine level during the initial query. Spring Data JPA generates a SELECT ... FOR UPDATE query. Any other transaction attempting to read or modify this row is blocked and queued until the current transaction commits or rolls back.

3. Feature Comparison

Lock Strategy Lock Location Performance Trade-off Best Fit
Optimistic (@Version) Application Level High throughput (No DB lock contention) Read-heavy, low conflict risk
Pessimistic (FOR UPDATE) Database Level Lower throughput (Holds DB locks, blocks threads) Write-heavy, high conflict risk

Practical Example

Quick Quiz

Q1: What exception is thrown if a concurrent update fails version check in an optimistic lock setup?

A) DeadlockLoserDataAccessException

B) ObjectOptimisticLockingFailureException (wrapping OptimisticLockException)

C) RollbackException

D) StaleConnectionException

Answer: B — Hibernate raises an OptimisticLockException which Spring translates into a standard locking failure exception.

Q2: When does a pessimistic write lock become released?

A) As soon as the select query completes execution.

B) Only when the Java application shuts down.

C) When the containing transaction commits or rolls back.

D) After a default timeout of 10 seconds.

Answer: C — Relational databases hold row-level locks for the complete duration of the active transaction session.

Scenario-Based Challenge

Production Scenario:

During a high-concurrency ticket sale event, users booking seats complain about receiving error pages stating "Optimistic locking conflict occurred". The UX is terrible because users have to click "Book" multiple times. What is the recommended fix?

View Solution

This happens because high contention causes version collisions. For hot-spots like seat bookings or stock items inventory:

  1. Switch to Pessimistic Locking: Query the booking status using LockModeType.PESSIMISTIC_WRITE. This forces threads to queue up and execute sequentially, preventing duplicate bookings and version retry conflicts.
  2. Implement Application Retry: Add an interceptor or use Spring's @Retryable to retry the booking operation automatically 3 times before returning an error to the user.

Interview Questions

1. Conceptual: What is a deadlock and how does it happen with pessimistic locking?

A deadlock happens when Transaction A locks Row 1 and waits for Row 2, while Transaction B locks Row 2 and waits for Row 1. Both transactions wait indefinitely. Database engines resolve this by forcibly terminating one of the transactions, throwing a deadlock exception.

2. Concept: How can you specify a lock timeout for pessimistic lock queries?

You can specify query hints inside the repository method:

Production Considerations

Use Optimistic Locking by default (adding a @Version column to all major entities). Switch to Pessimistic Locking only for high-concurrency write hot-spots where contention is high. Ensure that all locked columns are backed by database indexes to prevent lock escalation from row-level locks to full table scans.