
Written on 3rd September 2026 by Carter Phan.
Cả hai đều giải quyết concurrency / race condition, đặc biệt khi nhiều transaction cùng muốn sửa cùng một data.
Khác nhau ở tư duy:
Optimistic:
"Chắc ít conflict → cứ làm → lúc update mới check"
Pessimistic:
"Khả năng conflict cao → lock trước → thằng khác phải chờ"
Ví dụ:
Product A
stock = 10
Hai request cùng lúc:
User A User B
│ │
├── Read stock = 10 ────┤
│ │
├── stock = 9 │
│ ├── stock = 9
│ │
Kết quả có thể sai vì cả hai đều dựa trên cùng một state cũ.
Đây là race condition / lost update.
Không lock data ngay từ đầu. Giả định rằng conflict hiếm.
Flow:
Read
↓
Do business logic
↓
Update + kiểm tra version
↓
├── version đúng → SUCCESS
└── version sai → CONFLICT
Ví dụ database:
id stock version
1 10 5
Transaction A đọc:
stock = 10
version = 5
Transaction B cũng đọc:
stock = 10
version = 5
A update:
UPDATE productSET stock=9,
version=6WHERE id=1AND version=5;
→ affected rows = 1
A thành công.
B cố update:
UPDATE productSET stock=9,
version=6WHERE id=1AND version=5;
Nhưng database hiện tại:
version = 6
→ affected rows = 0
=> B biết:
"Data tao đọc đã bị người khác thay đổi."
Spring/JPA thường dùng:
@EntitypublicclassProduct {
@IdprivateLongid;privateintstock;
@VersionprivateLongversion;
}
Hibernate sẽ generate logic tương tự:
UPDATE productSET stock= ?,
version= ?WHERE id= ?AND version= ?;
Nếu version không match:
OptimisticLockException
Application có thể:
retry
reject request
ask user to refresh
tùy business.
Đây là điểm interview rất hay hỏi.
Tên Optimistic Locking có thể gây hiểu nhầm.
Nó không giữ database lock xuyên suốt transaction giống pessimistic locking.
Nó dựa vào:
version / timestamp / compare-and-set
để phát hiện conflict.
Giả định conflict có khả năng xảy ra, nên lock resource ngay từ đầu.
Flow:
BEGIN
↓
Acquire lock
↓
Read data
↓
Business logic
↓
Update
↓
COMMIT
↓
Release lock
Ví dụ PostgreSQL:
SELECT*FROM productWHERE id=1FORUPDATE;
Transaction A:
T1
│
├── SELECT ... FOR UPDATE
│
├── 🔒 lock row
│
├── UPDATE stock
│
└── COMMIT
Transaction B:
T2
│
├── SELECT ... FOR UPDATE
│
└── ⏳ WAIT
Sau khi T1 commit:
T1 → COMMIT
↓
unlock
↓
T2 → acquire lock
@Lock(LockModeType.PESSIMISTIC_WRITE)Optional<Product>findById(Longid);
Hibernate có thể generate:
SELECT*FROM productWHERE id= ?FORUPDATE;
Transaction giữ lock cho tới khi:
COMMIT
hoặc:
ROLLBACK
| Optimistic | Pessimistic | |
| Tư duy | Conflict hiếm | Conflict có thể cao |
| Lock trước? | ❌ | ✅ |
| Cách xử lý | Detect conflict | Prevent conflict |
| Cơ chế phổ biến | version | DB row lock |
| Conflict | Update fail | Request phải wait |
| Throughput | Tốt khi conflict thấp | Có thể giảm khi contention cao |
| Retry | Thường cần | Thường ít hơn |
| Deadlock | Ít hơn | Có thể xảy ra |
| Phù hợp | Read-heavy | Write/contention-heavy |
Hai admin cùng edit user:
Admin A đọc:
name = Thanh
version = 5
Admin B đọc:
name = Thanh
version = 5
A save:
Thanh → Carter
version 5 → 6
B save:
version = 5 ❌
→ Optimistic locking rất phù hợp.
Tại sao?
Conflict hiếm.
Không cần bắt B chờ trong suốt quá trình edit.
stock = 1
100 request cùng mua:
100 users
↓
same product
↓
stock = 1
Conflict cực cao.
Nếu dùng optimistic locking:
100 requests
↓
read stock = 1
↓
100 updates
↓
99 conflicts
↓
retry...
Có thể tạo retry storm.
Trong trường hợp này pessimistic locking hoặc atomic update / queue / Redis inventory reservation có thể phù hợp hơn.
Ví dụ:
T1
│
├── lock row
│
├── gọi Payment API
│
│ ⏳ 3 seconds
│
└── commit
Trong 3 giây:
T2 → WAIT
T3 → WAIT
T4 → WAIT
...
Đây là lý do:
Không nên giữ DB lock trong một operation dài hoặc gọi external service trong khi đang giữ lock.
Đặc biệt tránh:
BEGIN
↓
SELECT FOR UPDATE
↓
HTTP API
↓
Kafka
↓
external service
↓
COMMIT
Transaction càng lâu:
lock duration ↑
↓
contention ↑
↓
throughput ↓
Đây là use case quan trọng nhất để hiểu optimistic locking.
Initial:
balance = 100
Hai transaction:
T1 read = 100
T2 read = 100
T1 muốn:
100 + 50 = 150
T2 muốn:
100 + 20 = 120
Nếu không kiểm soát:
T1 → 150
T2 → 120
Final:
120
Thay đổi của T1 bị mất.
→ Lost Update
Database:
balance = 100
version = 5
T1:
UPDATE accountSET balance=150,
version=6WHERE id=1AND version=5;
Success.
T2:
UPDATE accountSET balance=120,
version=6WHERE id=1AND version=5;
affected rows = 0
→ Conflict detected.
T1:
SELECT*FROM accountWHERE id=1FORUPDATE;
→ lock.
T2:
SELECT*FROM accountWHERE id=1FORUPDATE;
→ wait.
Sau T1:
balance = 150
COMMIT
T2 mới đọc:
balance = 150
và tính:
150 + 20 = 170
Final:
170
Đây là cách nhớ tốt nhất:
Pessimistic
───────────
LOCK
↓
Prevent
conflict
Optimistic
──────────
READ
↓
WORK
↓
Check version
↓
Detect
conflict
Chọn khi:
Conflict thấp
+
Read nhiều
+
Transaction ngắn
+
Có thể retry / reject
Ví dụ:
Chọn khi:
Conflict cao
+
Resource critical
+
Không muốn nhiều request cùng modify
+
Transaction ngắn
Ví dụ:
lock duration
deadlock
timeout
contention
Có shared resource?
│
├── No → Không cần locking
│
↓ Yes
Conflict frequency?
│
├── Low
│ ↓
│ Optimistic
│
└── High
↓
Có thể atomic operation?
│
├── Yes → Atomic UPDATE / Redis atomic op
│
└── No
↓
Pessimistic Lock
Nếu contention cực cao:
Pessimistic lock
↓
có thể vẫn bottleneck
↓
Queue / serialization / reservation
"Optimistic lock tốt hơn vì không lock."
"Pessimistic lock an toàn hơn nên luôn dùng nó."
Optimistic locking tốt khi conflict thấp vì tránh locking và chấp nhận retry/conflict. Pessimistic locking phù hợp khi conflict cao và cần serialize access, nhưng phải trả giá bằng blocking và contention.