Imagine you’re working on a shared Google Doc with a colleague. You both open the same document, make your edits, and hit save. Now imagine if every time someone wanted to edit the document, they had to lock it so nobody else could even read it while they were making changes. That would be pretty frustrating, right? This is essentially the problem that optimistic concurrency control tries to solve in databases.
The Basics
Optimistic concurrency control (OCC) is a strategy for managing concurrent access to data in databases and other systems. The core philosophy is right there in the name. It’s optimistic. The system assumes that conflicts between transactions are rare, so it doesn’t lock resources upfront. Instead, it lets multiple transactions proceed simultaneously and only checks for conflicts when they’re ready to commit their changes.
So rather than making everyone wait in line to use a resource (pessimistic approach), OCC lets everyone work at the same time, and will address any issues as they arise.
How It Actually Works
The typical OCC implementation follows a three-phase approach:
- First, there’s the read phase, where a transaction reads all the data it needs and performs its computations. During this time, the transaction works with its own private copy of the data – it’s not blocking anyone else from accessing the same information.
- Next comes the validation phase. Before the transaction can commit its changes, the system checks whether any conflicts have occurred. It needs to verify that the data the transaction read at the beginning hasn’t been modified by another transaction in the meantime. If everything checks out (meaning no conflicts) the transaction moves to the final phase.
- The write phase is where the transaction actually commits its changes to the database. If the validation failed, the transaction gets rolled back, and typically it will retry the entire operation from scratch.
Version Numbers and Timestamps
So how does the system actually detect conflicts? The most common approach uses version numbers or timestamps. Every piece of data in the database has a version number attached to it. When a transaction reads data, it notes the current version number. When it’s time to commit, the system checks if the version number is still the same. If someone else has modified that data in the meantime, the version number will have changed, and boom, conflict detected.
Let’s say you’re building an e-commerce site, and two customers try to buy the last item in stock simultaneously:
- Customer A reads the inventory count (1 item, version #42),
- Customer B does the same thing a split second later (still 1 item, version #42).
- Both transactions proceed to checkout.
- When Customer A’s transaction tries to commit first, it checks the version number – still #42, so it commits successfully, updates the inventory to 0, and bumps the version to #43.
- When Customer B’s transaction tries to commit, it sees that the version is now #43, not #42, so it knows there’s been a conflict.
- Customer B’s transaction fails, and they get a “sorry, sold out” message.
Where Optimistic Concurrency Works Best
OCC works brilliantly in certain scenarios. If your application has mostly read-heavy workloads where conflicts are genuinely rare, OCC can provide excellent performance. You’re not wasting time and resources on locks that probably aren’t necessary.
Web applications are often a great fit for optimistic concurrency. Users typically read data, think about what they want to do (maybe they grab a coffee or get distracted by their phone), and then submit changes. The actual window where conflicts could occur is pretty small compared to the total time the user has the data “open”.
It’s also useful in distributed systems where coordinating locks across multiple servers would be expensive or complicated. Rather than maintaining lock state across a network, you can just check versions at commit time.
The Downsides
But optimistic concurrency control isn’t a silver bullet. When conflicts are actually common, the optimism becomes a liability. If transactions keep failing validation and having to retry, you can end up burning a lot of CPU cycles and making zero progress. It’s like if everyone in that shared Google Doc scenario kept overwriting each other’s work and having to start over – not so efficient anymore.
High-contention scenarios are OCC’s nemesis. If you have a banking system where thousands of transactions are all trying to update the same account balance simultaneously, the retry storm could be brutal. In these cases, pessimistic locking – where you grab a lock upfront and make others wait – might actually be more efficient.
There’s also the issue of transaction starvation. If a long-running transaction keeps getting invalidated by shorter transactions completing before it can commit, that poor transaction might never succeed. Some implementations add logic to prevent this, but it’s something to watch out for.
Pessimistic vs. Optimistic
The alternative to optimistic concurrency control is pessimistic concurrency control, which takes the “better safe than sorry” approach. Pessimistic locking assumes conflicts will happen, so it locks resources upfront. Other transactions have to wait their turn. It’s like checking out a library book – while you have it, nobody else can use it.
Neither approach is universally better. Pessimistic locking makes sense when contention is high and the cost of blocking is lower than the cost of retries. Optimistic locking makes sense when contention is low and you want maximum concurrency. Many modern systems actually use a hybrid approach, applying different strategies to different types of operations.
Real-World Applications
You’ll find optimistic concurrency control all over the place once you start looking for it. Git uses an optimistic approach – you can edit your local copy of code all day long, and conflicts only get detected when you try to merge. HTTP ETags work on similar principles, allowing browsers to cache resources and only update them if the version has changed.
Many NoSQL databases like CouchDB and DynamoDB offer optimistic concurrency through version checks or conditional updates. Even relational databases like MySQL, PostgreSQL, and SQL Server support optimistic locking patterns through row versioning and timestamp columns.
Object-relational mappers (ORMs) often provide built-in support for OCC. Entity Framework, Hibernate, and similar tools can automatically handle version checking for you, making it relatively painless to implement optimistic concurrency in your application code.
Summary
Optimistic concurrency control is a useful technique for managing concurrent access to shared data. It trades the upfront cost of locking for better concurrency and simpler code in low-contention scenarios. When conflicts are rare, OCC can provide excellent performance and a better user experience than pessimistic locking.
But it’s not magic. You need to understand your application’s access patterns and choose the right strategy accordingly. In practice, many successful systems use a mix of optimistic and pessimistic approaches, applying each where it makes the most sense. The key is knowing when to be optimistic and when to hedge your bets.