Introduction
In the previous article, we learned how Pessimistic Locking prevents concurrent modifications by locking database rows.
While this guarantees data consistency, it also means other users may have to wait until the lock is released.
But what if conflicts are rare?
Should we still lock every row?
Probably not.
This is where Optimistic Locking comes in.
Instead of preventing concurrent updates, Optimistic Locking allows multiple users to edit the same record and detects conflicts only when they try to save their changes.
It assumes that conflicts are unlikely, making it a great choice for many web applications.
What is Optimistic Locking?
Definition
Optimistic Locking is a concurrency control strategy that allows multiple transactions to read and modify the same record without acquiring database locks.
Before saving changes, Rails checks whether the record has been modified by another transaction.
If it has, Rails raises an exception instead of silently overwriting the newer data.
In simple words:
"I believe no one else will modify this record. But before saving, I'll verify that assumption."
Why Do We Need Optimistic Locking?
Imagine an admin panel.
Two administrators open the same product.
Current Product:
Title: iPhone 16
Price: ₹80,000
Admin A changes:
Price → ₹75,000
Admin B changes:
Title → iPhone 16 Pro
Without optimistic locking:
Admin A saves.
Database:
Title: iPhone 16
Price: ₹75,000
A few seconds later...
Admin B saves.
Database becomes:
Title: iPhone 16 Pro
Price: ₹80,000
Admin A's update is lost.
This is called a Lost Update.
How Rails Solves This
Rails provides built-in support for Optimistic Locking using a column named:
lock_version
Whenever a record is updated:
- Rails increments lock_version
- Rails verifies the previous version before updating
- If the version has changed, Rails raises an exception
Enabling Optimistic Locking
Simply add a column:
class AddLockVersionToProducts < ActiveRecord::Migration[8.0]
def change
add_column :products,
:lock_version,
:integer,
default: 0,
null: false
end
end
That's it.
Rails automatically enables optimistic locking.
No additional configuration is required.
Example
Current row:
Admin A loads:
lock_version = 0
Admin B also loads:
lock_version = 0
Admin A updates:
product.price = 75000
product.save!
Database:
Notice:
lock_version
became
1
Now Admin B tries:
product.title = "iPhone Pro"
product.save!
Rails generates SQL similar to:
UPDATE products SET title = 'iPhone Pro', lock_version = 2 WHERE id = 1 AND lock_version = 0;
But the database contains:
lock_version = 1
Therefore:
0 rows updated
Rails raises:
ActiveRecord::StaleObjectError
Timeline
Admin A
Read Product
Version = 0
↓
Edit
↓
Save
Version becomes 1
----------------------------
Admin B
Read Product
Version = 0
↓
Edit
↓
Try Save
↓
StaleObjectError
What Happens Internally?
Suppose current version:
lock_version = 3
Rails updates using:
UPDATE products
SET
price = 100,
lock_version = 4
WHERE
id = 1
AND lock_version = 3;
If another transaction already updated it:
lock_version = 4
The WHERE condition fails.
No row is updated.
Rails knows someone modified the record.
Handling the Exception
Typical implementation:
begin
product.update!(product_params)
rescue ActiveRecord::StaleObjectError
flash[:alert] =
"This product was updated by another user. Please reload and try again."
redirect_to edit_product_path(product)
end
Instead of silently overwriting data, the user is informed.
Real Production Example 1
CMS Article Editing
Two editors modify the same article.
Without optimistic locking:
One editor overwrites another's work.
With optimistic locking:
Second editor sees:
"This article has changed since you opened it."
Real Production Example 2
Admin Dashboard
Inventory manager changes:
Stock = 15
Manager B changes:
Price = ₹500
Instead of losing one update,
Rails detects the conflict.
Real Production Example 3
User Profile
Editing profile:
Name
Bio
Address
Conflicts are rare.
Blocking users would hurt UX.
Optimistic locking is a better fit.
Advantages
✅ No waiting
✅ Better scalability
✅ Better user experience
✅ No database locks
✅ Great for read-heavy applications
Disadvantages
❌ Save may fail
❌ Users must retry
❌ Not suitable for financial systems
❌ Doesn't prevent concurrent reads
Optimistic vs Pessimistic Locking



Top comments (0)