Bài toán: 2 request cùng đọc stock = 1, cùng thấy "còn hàng", cùng trừ → bán quá số tồn (oversell). Mẫu sai kinh điển là đọc rồi mới ghi (SELECT xong UPDATE) ở hai câu lệnh tách rời. Ba cách xử lý:
1. UPDATE có điều kiện (nguyên tử — nên dùng trước tiên):
UPDATE products SET stock = stock - 1
WHERE id = ? AND stock > 0;
-- 0 hàng bị ảnh hưởng → hết hàng, báo lỗi cho userMột câu lệnh duy nhất, DB tự khóa hàng khi ghi — không có khoảng hở giữa đọc và ghi.
2. Pessimistic — SELECT ... FOR UPDATE khi cần đọc + kiểm tra logic phức tạp trước khi ghi (giữ chỗ nhiều ghế, kiểm tra hạng vé):
BEGIN;
SELECT stock FROM products WHERE id = ? FOR UPDATE; -- khóa hàng, request khác chờ
-- kiểm tra nghiệp vụ rồi UPDATE
COMMIT;Đúng tuyệt đối nhưng tuần tự hóa truy cập vào hàng đó — throughput giảm khi tranh chấp cao.
3. Optimistic — cột version: UPDATE ... WHERE id = ? AND version = ?; 0 hàng bị ảnh hưởng → retry. Hợp tranh chấp thấp, tránh giữ khóa.
Thực tế hệ bán vé lớn còn thêm: Redis giữ chỗ tạm (đếm nguyên tử DECR + TTL) trước khi ghi DB, và hàng đợi để làm phẳng đỉnh tải. Điểm chốt khi trả lời: chỉ ra được khoảng hở check-then-act và chọn cơ chế theo mức tranh chấp.