Transaction là gì?
Transaction là một nhóm thao tác được thực hiện như một đơn vị: hoặc tất cả thành công, hoặc tất cả rollback.
Ví dụ chuyển tiền:
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 1000000 WHERE id = 1; -- Trừ tiền Alice
UPDATE accounts SET balance = balance + 1000000 WHERE id = 2; -- Cộng tiền Bob
COMMIT; -- Xác nhận cả 2 thao tác
Nếu bước 2 lỗi, bước 1 cũng bị rollback — Alice không mất tiền mà Bob không nhận được.
ACID Properties
| Property | Ý nghĩa | Ví dụ |
|---|---|---|
| Atomicity | Tất cả hoặc không gì | Chuyển tiền: trừ + cộng phải cùng thành công |
| Consistency | Dữ liệu luôn hợp lệ | Balance không bao giờ âm |
| Isolation | Transaction không ảnh hưởng lẫn nhau | 2 người chuyển tiền cùng lúc |
| Durability | Sau COMMIT, dữ liệu không mất | Mất điện sau COMMIT vẫn giữ data |
Isolation Levels
Đây là phần khó nhất của transaction — cân bằng giữa correctness và performance:
READ UNCOMMITTED → Nhanh nhất, ít an toàn nhất
READ COMMITTED → Default của PostgreSQL
REPEATABLE READ → Default của MySQL
SERIALIZABLE → Chậm nhất, an toàn nhất
Các vấn đề khi thiếu isolation
Dirty Read — Đọc dữ liệu chưa commit:
-- Transaction A -- Transaction B
UPDATE products SET price = 100;
SELECT price FROM products; -- Thấy 100
ROLLBACK; -- Nhưng 100 không tồn tại!
Non-Repeatable Read — Đọc 2 lần ra kết quả khác:
-- Transaction A -- Transaction B
SELECT price FROM products; -- 50
UPDATE products SET price = 100;
COMMIT;
SELECT price FROM products; -- 100 (khác lần trước!)
Phantom Read — Số lượng row thay đổi:
-- Transaction A -- Transaction B
SELECT COUNT(*) FROM orders; -- 10
INSERT INTO orders VALUES (...);
COMMIT;
SELECT COUNT(*) FROM orders; -- 11 (có row mới!)
ℹ️ Chọn Isolation Level nào?
- READ COMMITTED (default PostgreSQL): Đủ cho hầu hết ứng dụng web
- REPEATABLE READ: Khi cần đọc nhất quán trong transaction (báo cáo)
- SERIALIZABLE: Khi business logic yêu cầu tuyệt đối chính xác (tài chính)
Deadlock
Khi 2 transaction chờ nhau mãi mãi:
-- Transaction A -- Transaction B
UPDATE accounts SET ... WHERE id=1; -- Lock row 1
UPDATE accounts SET ... WHERE id=2; -- Lock row 2
UPDATE accounts SET ... WHERE id=2; -- Chờ row 2 (B đang giữ)
UPDATE accounts SET ... WHERE id=1; -- Chờ row 1 (A đang giữ)
-- DEADLOCK!
Cách tránh: Luôn lock theo thứ tự cố định (id nhỏ trước).
Best Practices
- Transaction ngắn nhất có thể — giữ lock ít thời gian
- Không gọi API bên ngoài trong transaction — nếu API chậm, lock bị giữ lâu
- Chọn isolation level phù hợp — không dùng SERIALIZABLE khi không cần
- Retry khi deadlock — database tự detect và kill 1 transaction