In this lesson, you will learn how to choose between Optimistic and Pessimistic locking strategies when multiple background workers compete to update shared persistent state.
When you have a queue of tasks that modify a shared database record—such as updating an account balance or a stock inventory—you must prevent "lost updates" caused by concurrent processes.
Pessimistic Locking: The "Safety First" Approach
Pessimistic locking assumes conflicts are highly likely. When a worker grabs a message from the queue, it immediately locks the corresponding row in the database (e.g., using SELECT ... FOR UPDATE in SQL). Other workers must wait until the first worker commits or rolls back its transaction.
- Pros: Prevents all race conditions at the database level.
- Cons: Can create bottlenecks and deadlocks, effectively turning your parallel background workers into a single-threaded process for that specific resource.
Optimistic Locking: The "Assume Success" Approach
Optimistic locking assumes conflicts are rare. Instead of locking the row, you add a Version or RowVersion column to your data. When a worker updates the record, it includes the version it read in the WHERE clause:
// Update only if the version hasn't changed since we read it
int affectedRows = await dbContext.Database.ExecuteSqlAsync(
"UPDATE Products SET Stock = @newStock, Version = Version + 1 " +
"WHERE Id = @id AND Version = @currentVersion");
if (affectedRows == 0) {
throw new DBConcurrencyException("State changed by another worker!");
}
- Pros: Highly scalable. No database locks are held during the processing time, allowing other reads to proceed unimpeded.
- Cons: If a conflict occurs, the worker must handle the exception, usually by retrying the operation or re-queuing the task.
Which to choose?
Use Pessimistic Locking when contention is extremely high (e.g., hundreds of workers competing for a single account balance), as the overhead of constant retries in optimistic locking would negate its performance benefits. Use Optimistic Locking for most background messaging scenarios where contention is low, as it keeps your system responsive and avoids the performance tax of long-held database locks.
Key Takeaway: Choose Pessimistic Locking when contention is unavoidable and high-cost; default to Optimistic Locking to maximize throughput and system concurrency.