
Written on 3rd September 2026 by Carter Phan.
Isolation quyết định: khi nhiều transaction chạy đồng thời, transaction này được phép nhìn thấy thay đổi của transaction kia đến mức nào.
Đây là một trong những phần quan trọng nhất của ACID vì nó là trade-off giữa correctness ↔ concurrency ↔ performance.
Giả sử có:
Account A = 1000
T1: Transfer 500 từ A → B
T2: Check balance của A
Hai transaction chạy đồng thời:
Time →
T1: UPDATE A = 500
T2: SELECT A
T1: COMMIT
Câu hỏi:
T2 đọc được1000hay500?
Câu trả lời phụ thuộc vào Isolation Level.
Nếu isolation quá thấp → transaction có thể đọc dữ liệu "lung tung".
Nếu isolation quá cao → transaction phải chờ nhau nhiều → throughput giảm.
Trước khi học 4 isolation levels, cần hiểu 3 anomaly chính.
READ UNCOMMITTED
↓
Dirty Read
↓
READ COMMITTED
↓
Non-repeatable Read
↓
REPEATABLE READ
↓
Phantom Read
↓
SERIALIZABLE
Đây là cách nhớ cơ bản, nhưng thực tế phụ thuộc database/implementation.
Transaction đọc dữ liệu mà transaction khác chưa COMMIT.
Ví dụ:
Initial balance = 1000
T1 T2
│ │
│ UPDATE balance = 500 │
│ │
│ SELECT balance
│ │
│ → 500
│ │
│ ROLLBACK │
│ │
T2 đã đọc:
500
nhưng cuối cùng database quay lại:
1000
→ T2 đọc một giá trị chưa bao giờ thực sự tồn tại sau cùng.
Đây gọi là:
Dirty Read
Trong cùng một transaction, đọc cùng một row 2 lần nhưng nhận 2 kết quả khác nhau.
Ví dụ:
Initial balance = 1000
T1 T2
│ │
│ SELECT balance │
│ → 1000 │
│ │
│ │ UPDATE = 500
│ │ COMMIT
│ │
│ SELECT balance │
│ → 500 │
T1:
First read → 1000
Second read → 500
Cùng một transaction nhưng kết quả thay đổi.
→ Non-repeatable Read
Đây là phần dễ nhầm nhất.
Transaction chạy cùng một query nhưng lần sau xuất hiện thêm hoặc mất rows do transaction khác INSERT/DELETE.
Ví dụ:
T1:
SELECT * FROM orders
WHERE amount > 100;
→ 3 rows
Trong lúc đó:
T2:
INSERT INTO orders(amount)
VALUES (200);
COMMIT;
T1 chạy lại:
SELECT*FROM ordersWHERE amount>100;
Kết quả:
→ 4 rows
Row mới xuất hiện như một phantom.
| Anomaly | Vấn đề |
| Dirty Read | Đọc data chưa commit |
| Non-repeatable Read | Cùng row nhưng giá trị thay đổi |
| Phantom Read | Query trả thêm/bớt rows |
Ví dụ cực ngắn:
Dirty:
T1 chưa commit → T2 đã thấy
Non-repeatable:
T1 đọc row → T2 sửa → T1 đọc lại → khác
Phantom:
T1 query 3 rows → T2 INSERT → T1 query lại → 4 rows
SQL standard định nghĩa:
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE
Từ weak → strong:
Isolation
↑
│
Serializable │ strongest
│
Repeatable │
Read │
│
Read │
Committed │
│
Read │
Uncommitted │ weakest
└────────────→ concurrency
Thông thường:
Isolation càng mạnh → càng ít anomaly → nhưng concurrency có thể giảm.
Transaction có thể đọc cả dữ liệu chưa commit.
T1 T2
UPDATE balance=500
SELECT balance
→ 500
ROLLBACK
T2 đọc được 500.
Nhưng sau rollback:
balance = 1000
→ Dirty Read.
Dirty Read ✓
Non-repeatable ✓
Phantom ✓
Gần như không nên dùng cho business transaction.
Có thể phù hợp với một số workload reporting/approximate statistics nếu chấp nhận dữ liệu không chính xác, nhưng hiện đại database thường có lựa chọn tốt hơn.
Đây là isolation level rất phổ biến.
Chỉ đọc dữ liệu đã COMMIT.
Ví dụ:
T1 T2
UPDATE balance = 500
SELECT balance
→ 1000
COMMIT
SELECT balance
→ 500
T2 không thấy dirty data.
Dirty Read ❌
Non-repeatable ✓
Phantom ✓
Tức là:
T1 đọc → 1000
T2 UPDATE → 500
T2 COMMIT
T1 đọc lại → 500
T1 vẫn có thể thấy data thay đổi.
PostgreSQL mặc định sử dụng:
READ COMMITTED
và sử dụng MVCC để quản lý concurrent access.
Điểm quan trọng:
READ COMMITTED không nhất thiết có nghĩa là database phải lock mọi row mà bạn đọc.
Mục tiêu:
Trong cùng một transaction, nếu đọc một row nhiều lần thì kết quả phải ổn định theo snapshot của transaction.
Ví dụ:
Initial:
balance = 1000
T1 T2
BEGIN
SELECT balance
→ 1000
UPDATE balance = 500
COMMIT
SELECT balance
→ 1000
T1 vẫn nhìn thấy:
1000
vì T1 đang làm việc với snapshot của mình.
READ COMMITTED:
SELECT → 1000
↓
T2 UPDATE + COMMIT
↓
SELECT → 500
REPEATABLE READ:
SELECT → 1000
↓
T2 UPDATE + COMMIT
↓
SELECT → 1000
Đây là một trick interview rất hay.
Theo SQL standard:
REPEATABLE READ không bắt buộc phải ngăn Phantom Read.
Nhưng implementation của database có thể mạnh hơn standard.
Ví dụ PostgreSQL:
REPEATABLE READ
sử dụng snapshot isolation và không cho transaction thấy các row commit sau snapshot của nó.
Vì vậy nhiều trường hợp phantom theo định nghĩa thông thường sẽ không xuất hiện.
Nhưng:
Đừng trả lời máy móc rằng REPEATABLE READ luôn giống nhau trên mọi database.
Isolation semantics phụ thuộc implementation.
Đây là isolation mạnh nhất.
Ý tưởng:
Kết quả của các transaction concurrent phải tương đương với việc chúng chạy tuần tự.
Ví dụ:
T1 ────────────────
T2 ────────────────
Database phải đảm bảo kết quả tương đương với:
T1
↓
T2
hoặc:
T2
↓
T1
Giả sử:
Inventory = 1
Hai user cùng mua:
T1: buy 1
T2: buy 1
Nếu không có isolation phù hợp:
T1 read stock = 1
T2 read stock = 1
T1 → stock = 0
T2 → stock = 0
→ cả hai đều tưởng mua thành công ❌
Serializable phải ngăn kết quả này.
Có thể xảy ra:
T1 → SUCCESS
T2 → serialization failure
Application phải:
retry T2
Đây là misconception phổ biến.
Có nhiều cách implement:
2PL / locking
MVCC
Serializable Snapshot Isolation
...
Ví dụ PostgreSQL sử dụng cơ chế Serializable Snapshot Isolation (SSI) cho SERIALIZABLE.
Do đó:
Isolation level là semantic guarantee, không nên đồng nhất nó với một implementation cụ thể như "lock row".
| Isolation | Dirty Read | Non-repeatable | Phantom | Concurrency |
| READ UNCOMMITTED | ✓ | ✓ | ✓ | ⭐⭐⭐⭐ |
| READ COMMITTED | ❌ | ✓ | ✓ | ⭐⭐⭐ |
| REPEATABLE READ | ❌ | ❌ | DB-dependent | ⭐⭐ |
| SERIALIZABLE | ❌ | ❌ | ❌ | ⭐ |
✓ = có thể xảy ra
❌ = được ngăn theo isolation semantics
Cực kỳ quan trọng.
Isolation level:
Bạn được phép nhìn thấy gì?
Lock:
Ai được phép thay đổi/đọc gì tại thời điểm này?
Ví dụ:
SELECT*FROM accountsWHERE id=1FORUPDATE;
Đây là pessimistic locking.
Nó không đơn giản đồng nghĩa với:
SERIALIZABLE
Bạn có thể sử dụng explicit lock trong:
READ COMMITTED
để giải quyết một business concurrency problem cụ thể.
Modern database thường sử dụng:
MVCC — Multi-Version Concurrency Control
Thay vì:
Reader lock → Writer phải chờ
database có thể giữ nhiều version của row.
Ví dụ:
balance = 1000
↓
UPDATE
↓
balance = 500
Có thể tồn tại conceptually:
Version 1 → 1000
Version 2 → 500
Transaction nào nhìn thấy version nào phụ thuộc vào:
transaction snapshot
+
isolation level
Điều này cho phép:
Reader ──────────────→
Writer ─────────→
cùng tiến hành trong nhiều trường hợp mà không phải lock toàn bộ.
Isolation càng mạnh không đơn giản là:
stronger = slower
nhưng thường có cost cao hơn về contention/conflict/retry.
Ví dụ:
T1 ──────────→
T2 ──────────→
T3 ──────────→
T4 ──────────→
Concurrency tốt.
Có thể:
T1 ──────────→ SUCCESS
T2 ─── conflict ──→ ABORT
↓
RETRY
T3 ─── conflict ──→ ABORT
Khi contention cao:
conflict ↑
retry ↑
throughput ↓
Không nên suy nghĩ:
"Production thì cứ SERIALIZABLE cho an toàn."
Thay vào đó:
Business requirement
↓
Consistency requirement
↓
Concurrency requirement
↓
Choose isolation
↓
Add explicit locking / optimistic locking nếu cần
Phù hợp cho:
CRUD
normal transactions
web applications
most business operations
Khi cần:
consistent snapshot
multiple reads trong transaction
reporting / complex transaction
Khi correctness quan trọng đến mức:
concurrent transactions
↓
must behave as serial execution
Ví dụ một số financial/critical business logic, nhưng cần benchmark và thiết kế retry.
Giả sử:
stock = 1
1000 users
↓
SELECT stock
↓
if stock > 0
↓
UPDATE stock = stock - 1
Dù dùng isolation level, nếu business logic sai:
1000 requests
↓
1000 transactions
↓
contention
thì vấn đề không đơn giản là:
"Dùng SERIALIZABLE là xong."
Có thể thiết kế:
UPDATE productSET stock= stock-1WHERE id=1AND stock>0;
Sau đó:
affected rows = 1
→ success
affected rows = 0
→ sold out
Hoặc dùng:
Redis atomic operation
Queue
Inventory reservation
Optimistic locking
Pessimistic locking
Tức là:
Isolation level là một tool, không phải toàn bộ concurrency strategy.
DIRTY NON-REPEATABLE PHANTOM
READ READ READ
│ │ │
READ UNCOMMITTED ✓ ✓ ✓
│
READ COMMITTED ✗ ✓ ✓
│
REPEATABLE READ ✗ ✗ DB-dependent
│
SERIALIZABLE ✗ ✗ ✗
│
└────── stronger ────────────→
isolation