Marcio Cunha

Optimistic vs Pessimistic Locking: Concurrency Control Strategies in Databases

Understand the fundamental differences between optimistic and pessimistic locking in concurrent systems. Learn when to apply each approach to ensure data consistency without sacrificing performance.

Marcio Cunha11 min
Also available in:EspañolPortuguês
Summary
  • Pessimistic locking assumes data conflicts are frequent and secures the record right at the beginning of the transaction.
  • Optimistic locking bets that simultaneous changes are rare and validates the state only at the moment of saving.
  • Systems with high write contention and low shared data volume benefit greatly from the pessimistic model.
  • Scalable applications with massive reads avoid operational bottlenecks by adopting version-based checks in the optimistic model.
  • Optimistic concurrency errors require robust exception-handling strategies and client-side retry mechanisms.

The Silent Challenge of Concurrency in Software Systems

Imagine two people trying to buy the exact last ticket for a concert at the exact same millisecond. If the booking system fails to coordinate these actions with precision, the same seat might be sold twice, leading to a massive operational headache. In software engineering, we call this scenario concurrency, which occurs when multiple users or processes attempt to modify the exact same data at the same time. To prevent chaos from taking over databases, engineers use strategies called locking mechanisms or concurrency control. In practice, these techniques work like traffic rules at a busy intersection, dictating who goes first and who must wait their turn.

Without proper control, software suffers from dreaded data corruption and race conditions, which happen when the outcome of an operation depends on the unpredictable order in which instructions are executed by the computer. This is where two fundamental and opposing philosophies come into play: Pessimistic Locking and Optimistic Locking. Each approach views the risk of conflict in a completely different way, shaping performance, scalability, and the architecture of modern applications. Understanding when and how to apply each model is an indispensable skill for building reliable systems that do not crash when traffic spikes.

Understanding Pessimistic Locking in Practice

Pessimistic locking starts from a cautious and distrustful premise: the principle that conflict is inevitable. In practice, when a process decides to read data it plans to change shortly after, it immediately places a physical padlock on that information inside the database. This padlock prevents any other transaction from reading or modifying the same record until the first process finishes its work and releases access. It is the equivalent of locking a meeting room door from the inside; anyone trying to enter afterward has to knock and wait outside, unable to even peek at what is happening inside.

At the technical level, this strategy uses specific SQL commands, such as SELECT ... FOR UPDATE. When the database receives this instruction, it reserves memory space and applies locks to the affected rows. While this approach guarantees absolute safety against conflicting simultaneous writes, it extracts a heavy toll in terms of performance. If many users attempt to access popular resources simultaneously, massive queues form, connections exhaust their timeouts, and the entire system can suffer a drastic slowdown or even crash completely due to resource contention.

BEGIN TRANSACTION;-- The database immediately locks the row against concurrent reads and writesSELECT balance FROM accounts WHERE id = 42 FOR UPDATE;-- Business logic processing...UPDATE accounts SET balance = balance - 100 WHERE id = 42;COMMIT;

Exploring the Optimistic Locking Approach

In stark contrast to the distrust of the previous model, optimistic locking takes a more relaxed and positive stance: the belief that collisions and simultaneous conflicts are rare events in daily application usage. Instead of locking the data from the start of the read phase, the system allows multiple users to read and modify copies of the data freely and at the same time. The grand moment of truth occurs only at the final saving moment, when the system verifies whether anyone else altered that exact same record in the interval between the initial read and the current save attempt.

For this verification to work without physical locks, database tables typically include an extra control column, frequently named version or revision. Each time a row is successfully modified, this version number is incremented automatically. When saving, the SQL command checks if the version in the database is still exactly the same one the user read at the beginning of the operation. If the numbers match, the change is accepted and the version advances by one. If the numbers differ, it means someone else was faster and altered the data halfway through; in this case, the operation is rejected and the system notifies the user about the conflict.

-- The user read the data and the current version was 3UPDATE products SET stock = stock - 1, version = version + 1WHERE id = 100 AND version = 3;-- If no rows were affected, it means the version changed and a conflict occurred

Comparative Analysis: Advantages and Operational Costs

The choice between the optimistic and pessimistic models is not a matter of aesthetic preference, but a profound architectural trade-off. Pessimistic locking shines in highly competitive scenarios where the cost of an error is catastrophic, such as in traditional banking systems or inventory control for extremely scarce products during a massive flash sale. In these environments, preventing the error at the source outweighs the slowness generated by waiting queues. On the flip side, it fails miserably in large-scale web applications where thousands of users browse and update data sparsely, because keeping connections locked exhausts database server resources rapidly.

Conversely, optimistic locking is the undisputed champion of scalability in modern distributed and microservices-based systems. Since it does not maintain active locks on the network or the database while the user fills out forms or makes decisions, resource consumption remains extremely low. However, it introduces a new type of operational complexity: handling concurrency exceptions. When a conflict occurs, the developer must program the system to handle it gracefully, either by notifying the user to try again or by implementing background automated retry routines.

Real-World Scenarios and Architecture Decisions

To illustrate the practical application of these theories, consider a cloud-based collaborative document editing system, similar to Google Docs. If the system adopted pessimistic locking, only one person could open the file in edit mode at a time, blocking access for all teammates until they closed their browser tab. This would render teamwork unviable. Therefore, a variation of optimistic locking is used: changes are sent in small chunks, and if a paragraph conflict arises, the system merges the edits or asks the author to review the modified section written by a colleague moments before.

By contrast, consider processing payments on a credit card machine that debits the balance of a specific bank account. In this strict context of direct financial transactions, a millisecond delay and the use of pessimistic locking are fully justified and necessary. The risk of allowing two simultaneous withdrawals exceeding the account limit far outweighs the performance penalty imposed by the database waiting queue. Software engineering ultimately boils down to evaluating the problem domain and choosing the tool whose risk philosophy aligns perfectly with the company's business goals.

Final Considerations on Concurrency Strategies

Mastering concurrency control techniques separates fragile systems that break under pressure from robust architectures capable of sustaining millions of daily accesses without corrupting a single byte. Both pessimistic and optimistic locking offer elegant solutions to the universal challenge of managing shared states, but they operate under entirely opposite behavioral assumptions about human and computational behavior. The secret to success does not lie in choosing a single silver bullet, but in analyzing the application's load profile and applying each strategy precisely where it delivers maximum technical value.

When designing new features or refactoring legacy code, always question the real frequency of conflicts in your business domain and the financial impact of a concurrency error. With this analytical mindset, choosing between locking the record upfront or taking a risk and validating at the end ceases to be a guess and becomes a mature, well-founded engineering decision.