Cùng ôn lại các loại transaction nhé! Trong thế giới transaction của database, có ba "kẻ phản diện" chính có thể phá hỏng ngày của bạn: Dirty Read, Non-Repeatable Read và Phantom Read. Mấy "bất thường" này xuất hiện do isolation level của transaction chưa đủ mạnh. Hôm nay mình sẽ giải thích từng kẻ phản diện này là ai, chúng xuất hiện thế nào, và — quan trọng nhất — làm sao để xử lý chúng.
Trước khi vào ví dụ, cùng nhớ lại ý nghĩa của từng cái nhé.
Dirty Read (Đọc bẩn):
Bạn đọc dữ liệu đã bị thay đổi, nhưng transaction thay đổi nó vẫn chưa commit (COMMIT) hoặc tệ hơn là có thể bị rollback (ROLLBACK). Kiểu như bạn chuyển tiền cho bạn, nhìn số dư thấy hết sạch tiền, nhưng sau đó lại đổi ý và lấy lại tiền. Ảo thật đấy!Non-Repeatable Read (Đọc không lặp lại):
Bạn đọc cùng một dữ liệu hai lần trong một transaction, nhưng giữa hai lần đọc, dữ liệu đó bị transaction khác thay đổi, và bạn thấy hai kết quả khác nhau. Giống như bạn nhìn ngày sinh trong hộ chiếu, rồi đưa cho bạn chỉnh sửa số, sau đó nhìn lại thì ngày sinh đã khác rồi.Phantom Read (Đọc ảo):
Bạn chạy cùng một query hai lần, nhưng lần thứ hai lại thấy thêm dòng mới do transaction khác vừa thêm vào. Kiểu như bạn đếm người trong phòng, ai đó lén dẫn thêm bạn vào mà bạn không biết.
Vấn đề Dirty Read
Giả sử mình có bảng accounts:
CREATE TABLE accounts (
account_id SERIAL PRIMARY KEY,
owner TEXT NOT NULL,
balance NUMERIC(10, 2) NOT NULL
);
INSERT INTO accounts (owner, balance) VALUES ('Alice', 1000), ('Bob', 500);
Transaction 1 thay đổi balance, nhưng chưa xong, còn Transaction 2 lại cố đọc dữ liệu đó cùng lúc.
Transaction 1:
BEGIN;
UPDATE accounts SET balance = balance - 200 WHERE owner = 'Alice';
-- Số dư của Alice thành 800, nhưng transaction vẫn chưa xong.
Transaction 2:
BEGIN;
SELECT balance FROM accounts WHERE owner = 'Alice'; -- Thấy số dư: 800 (đọc bẩn).
ROLLBACK; -- Transaction 1 bị rollback.
Bây giờ Transaction 2 lại dùng dữ liệu sai, vì Transaction 1 đã huỷ thay đổi. Có thể tránh chuyện này nếu dùng isolation level READ COMMITTED, nó sẽ không cho thấy thay đổi từ transaction chưa commit.
Vấn đề Non-Repeatable Read
Hãy tưởng tượng Transaction 1 đọc dữ liệu, transaction khác thay đổi nó, rồi Transaction 1 lại đọc lần nữa. Dữ liệu đã khác rồi.
Transaction 1:
BEGIN;
SELECT balance FROM accounts WHERE owner = 'Bob'; -- Thấy số dư: 500.
Transaction 2 song song:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE owner = 'Bob';
COMMIT;
Transaction 1 lại tiếp tục:
SELECT balance FROM accounts WHERE owner = 'Bob'; -- Thấy số dư: 400.
COMMIT;
Thấy không, dữ liệu trong cùng một transaction mà lại thay đổi. Trong thực tế, chuyện này có thể rất nghiêm trọng, ví dụ như khi làm báo cáo tài chính. Để giải quyết, hãy dùng isolation level cao hơn, ví dụ REPEATABLE READ.
Vấn đề Phantom Read
Giả sử mình có bảng orders:
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
customer TEXT NOT NULL,
total NUMERIC(10, 2) NOT NULL
);
INSERT INTO orders (customer, total) VALUES ('Alice', 100), ('Bob', 200);
Transaction 1 đếm số lượng order, transaction khác thêm order mới, rồi Transaction 1 lại đếm tiếp.
Transaction 1:
BEGIN;
SELECT COUNT(*) FROM orders; -- Thấy: 2.
Transaction 2 song song:
BEGIN;
INSERT INTO orders (customer, total) VALUES ('Charlie', 300);
COMMIT;
Transaction 1 lại tiếp tục:
SELECT COUNT(*) FROM orders; -- Thấy: 3. Đơn hàng "ảo" mới xuất hiện!
COMMIT;
Để loại bỏ phantom read, cần dùng isolation level SERIALIZABLE, nó sẽ khoá hoàn toàn các thay đổi song song ảnh hưởng đến kết quả.
Cách phòng tránh bất thường
Isolation level vs. Bất thường
| Isolation level | Dirty Read |
Non-Repeatable Read |
Phantom Read |
|---|---|---|---|
Read Uncommitted |
❌ Có | ❌ Có | ❌ Có |
Read Committed |
✅ Không | ❌ Có | ❌ Có |
Repeatable Read |
✅ Không | ✅ Không | ❌ Có |
Serializable |
✅ Không | ✅ Không | ✅ Không |
Làm sao chọn đúng isolation level?
- Nếu cần truy cập dữ liệu nhanh và bất thường không quá quan trọng (ví dụ phân tích dữ liệu cũ) — dùng
READ COMMITTED. - Nếu cần dữ liệu ổn định trong một transaction — dùng
REPEATABLE READ. - Nếu cần cách ly và nhất quán tối đa — chọn
SERIALIZABLE. Nhớ là hiệu năng có thể giảm đấy.
Mẹo thực tế
Dùng transaction với isolation level phù hợp cho trường hợp của bạn. Ví dụ:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT ...;
COMMIT;
Thêm index để giảm khoá bảng và tăng tốc query.
Dùng query tối ưu để tránh khoá lâu, nhất là ở level SERIALIZABLE.
Lỗi đặc thù và cách tránh
Đôi khi chọn sai isolation level sẽ gây conflict và giảm hiệu năng. Ví dụ, dùng SERIALIZABLE cho hệ thống có nhiều transaction song song sẽ dẫn đến khoá và "đói" transaction.
Để tránh chuyện này, hãy phân tích query của bạn, test hiệu năng ở các isolation level khác nhau và dùng index hợp lý.
GO TO FULL VERSION