Foundation · Build
Locking, Indexes & Query Plans
Optimistic @Version detects conflicts after the fact; pessimistic locks prevent them outright—and neither compensates for a query plan scanning the table.
Primary source: Spring Data JPA: Lock Mode Annotations
Lesson 15 of 17
1d streakThe Architectural Question
Lost updates corrupt inventory silently without version checks, while teams add indexes by folklore instead of reading plans—so the slow query stays slow and writes get slower.
What this guide proves
- Add @Version and handle OptimisticLockingFailureException with bounded retries
- Reserve hot rows with pessimistic write locks only when contention is real
- Read EXPLAIN output before creating or blaming indexes
Optimistic vs Pessimistic locking: Concurrency control
Architectural Boundary · Do Not Confuse
Optimistic Locking (@Version)
Pessimistic Locking (SELECT ... FOR UPDATE)
Optimistic locking adds a version column to the entity. No database locks are held while reading. When updating, Hibernate checks `WHERE id = ? AND version = ?`. If another transaction updated the row first, an OptimisticLockException is thrown. Pessimistic locking issues a database lock (`SELECT ... FOR UPDATE`), blocking competing transactions until the lock is released.
Analyzing query plans with EXPLAIN (ANALYZE, BUFFERS)
Writing JPQL without verifying the underlying SQL plan risks severe full table scans. Inspect the PostgreSQL query plan to ensure indices are utilized:
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE customer_id = '7b8c9d0e'; Bitmap Heap Scan on orders (cost=4.28..12.35 rows=4 width=128) (actual time=0.042..0.051 rows=3 loops=1) Recheck Cond: (customer_id = '7b8c9d0e') Buffers: shared hit=3 -> Bitmap Index Scan on idx_orders_customer_id (cost=0.00..4.28 rows=4 width=0) (actual time=0.015..0.015 rows=3 loops=1) Planning Time: 0.120 ms Execution Time: 0.082 ms
Active Recall Checkpoint
When should you choose pessimistic locking over optimistic locking?
Hint: Consider contention volume.
Show answer & explanation
When collision frequency is high (e.g. inventory counters, ticket reservations). Retrying optimistic locking exceptions under high contention creates CPU thrashing; pessimistic locking queues concurrent transactions predictably.
Locking strategy guidelines
Use it when
Default to Optimistic Locking (@Version) for standard enterprise entities where collisions are rare.
Reconsider when
Use Pessimistic Locking with a short timeout (@Lock(LockModeType.PESSIMISTIC_WRITE)) for scarce resources like flash-sale inventory.
Runtime Architecture & Flow
Persistence context · runtime visualization
Locking, Indexes & Query Plans
Transaction
Add @Version and handle OptimisticLockingFailureException with bounded retries
Entity state
Reserve hot rows with pessimistic write locks only when contention is real
Flush
Read EXPLAIN output before creating or blaming indexes
Commit
Run concurrent stock reservations against @Version, then fix a slow filter query with one composite index verified by EXPLAIN.
Working Implementation
Code Walkthrough
Run concurrent stock reservations against @Version, then fix a slow filter query with one composite index verified by EXPLAIN.
@Entity
class ProductStock {
@Id private UUID id;
@Version private long version;
private int available;
void reserve(int quantity) {
if (quantity > available) throw new InsufficientStock();
available -= quantity;
}
}
interface ProductStockRepository extends JpaRepository<ProductStock, UUID> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select p from ProductStock p where p.id = :id")
Optional<ProductStock> findForUpdate(UUID id);
}
// Verify supporting indexes with EXPLAIN ANALYZE, not guesswork.
// create index idx_products_active_name on products(active, lower(name));Synthesis & Production Verification
Deliberate Failure Condition
- Break: Two threads reserve the last unit with plain reads and writes
- Observe: Both succeed; available goes negative
- Repair: Check-then-act across transactions needs optimistic versioning or a pessimistic lock; application-level if statements cannot see other transactions.
Production Acceptance Criteria
- Force an optimistic conflict and retry it successfully
- Demonstrate a pessimistic lock blocking a second reader
- Halve a query’s plan cost with an evidence-backed index
Key Takeaway
Optimistic locking (@Version) handles infrequent collisions gracefully; pessimistic locking serializes high-contention access, verified with EXPLAIN.
Architectural & Technical Review
Core Interview Questions
Q01When is optimistic locking preferable to pessimistic locking?Show answer
When concurrent update collisions are infrequent, avoiding long-held database row locks.
Q02How does EXPLAIN (ANALYZE, BUFFERS) help diagnose slow queries?Show answer
It shows the actual execution time, index scans vs sequential scans, and shared buffer hits in PostgreSQL.
Q03When should pessimistic write locks be employed?Show answer
For scarce, high-contention resources like flash-sale inventory or seat bookings where optimistic retries cause CPU thrashing.
Production Pitfalls
Critical Anti-Patterns
- Pitfall: Omitting timeout configurations on pessimistic locks — Acquiring pessimistic locks without timeouts risks hanging application threads indefinitely during deadlocks.
- Pitfall: Deploying queries without analyzing execution plans — Queries that look fast on small development datasets can trigger full table scans and timeouts in production.
Primary Documentation & Specifications
Lesson Primary References
Supplementary Module Resources