Họ đang đo gì
Bạn có định lượng được tranh chấp không, hay chỉ chọn theo thói quen của framework.
Trả lời ngắn~30 giây
Khoá bi quan (SELECT … FOR UPDATE) giữ hàng cho tới khi commit: không ai mất công làm lại, nhưng mọi người xếp hàng. Khoá lạc quan (cột version cộng WHERE version = $1) không giữ gì cả, chỉ phát hiện xung đột lúc ghi và bắt người thua làm lại. Tranh chấp thấp thì lạc quan thắng rõ; tranh chấp cao thì lạc quan biến thành vòng lặp retry đốt CPU, lúc đó bi quan lại rẻ hơn.
Giải thích sâu
Con số cần ước lượng là xác suất hai transaction chạm cùng một hàng trong khoảng thời gian chúng cùng sống. Sửa hồ sơ người dùng: gần như bằng không, vì mỗi người sửa hồ sơ của mình — dùng lạc quan, và phần lớn thời gian bạn thậm chí không cần khoá gì. Trừ tồn kho của một mã hàng đang cháy hàng trong đợt flash sale: gần như chắc chắn đụng nhau, lạc quan sẽ retry hàng nghìn lần và tình hình tệ đi khi thêm máy chủ.
Có một lựa chọn thứ ba mà ứng viên hay quên: viết lại thành một câu lệnh nguyên tử. UPDATE stock SET qty = qty - 1 WHERE sku = $1 AND qty > 0 không cần khoá tường minh nào, không có read-modify-write, và số hàng bị ảnh hưởng cho bạn biết thành công hay không. Khi bài toán cho phép biểu diễn kiểu này thì nó luôn tốt hơn cả hai phương án kia.
Khoá bi quan có một cái bẫy vận hành: nó giữ khoá đến hết transaction, nên nếu bạn gọi API bên ngoài giữa FOR UPDATE và COMMIT, độ trễ của bên thứ ba trở thành thời gian giữ khoá của bạn. Đây là nguyên nhân rất phổ biến của những đợt nghẽn kết nối: một cổng thanh toán chậm đi 2 giây và cả bảng đơn hàng đứng lại.
Ba cách, cùng một nghiệp vụ
-- Bi quan: ai tới sau thì chờ
BEGIN;
SELECT qty FROM stock WHERE sku = 'A1' FOR UPDATE;
UPDATE stock SET qty = qty - 1 WHERE sku = 'A1';
COMMIT;
-- Lạc quan: không chờ, thua thì làm lại
UPDATE stock SET qty = qty - 1, version = version + 1
WHERE sku = 'A1' AND version = 42; -- 0 hàng bị sửa = có người nhanh hơn
-- Nguyên tử: không khoá, không retry, không cửa sổ đua
UPDATE stock SET qty = qty - 1 WHERE sku = 'A1' AND qty > 0;Câu hỏi tiếp theo họ sẽ hỏi
?FOR UPDATE và FOR NO KEY UPDATE khác gì?
FOR NO KEY UPDATE yếu hơn: nó không chặn transaction khác tạo khoá ngoại trỏ tới hàng này. Trong Postgres, UPDATE không đụng cột khoá sẽ tự lấy mức yếu hơn đó, nên dùng đúng mức giảm được tranh chấp mà không mất an toàn.
?Deadlock xảy ra thế nào với khoá bi quan?
Khi hai transaction khoá cùng tập hàng theo thứ tự ngược nhau. Cách phòng rẻ nhất là luôn khoá theo một thứ tự cố định — ví dụ sắp xếp id tăng dần trước khi khoá. Engine sẽ tự phát hiện và huỷ một bên, nên ứng dụng vẫn cần retry.
Trả lời thế này là mất điểm
- Chọn theo mặc định của ORM mà không nói được vì sao. JPA có
@Versionsẵn, nhưng dùng nó cho hàng nóng là chọn sai. - Không nhắc tới phương án viết lại thành một câu lệnh. Đó thường là câu trả lời đúng nhất.