Đây là lost update. Cách xử lý theo chuẩn HTTP là optimistic locking bằng conditional request.
1. GET trả về resource kèm ETag — một định danh cho phiên bản hiện tại của bản ghi (hash nội dung, hoặc version/updated_at mã hoá lại).
2. Client sửa xong thì gửi lại phiên bản mình đã đọc trong header If-Match.
3. Server so If-Match với ETag hiện tại: khớp thì ghi, không khớp thì từ chối bằng 412 Precondition Failed — nghĩa là "bản ghi đã bị người khác đổi từ lúc bạn đọc".
GET /articles/9 -> 200 OK, ETag: "v7"
PUT /articles/9
If-Match: "v7"
-> 200 OK, ETag: "v8" (ghi thành công)
PUT /articles/9
If-Match: "v7"
-> 412 Precondition Failed (người khác đã đẩy lên v8)Các điểm hay bị hỏi sâu thêm:
- Kiểm tra và ghi phải nguyên tử. So ETag ở tầng application rồi mới UPDATE là vẫn còn race. Đưa điều kiện vào chính câu lệnh ghi: update articles set ..., version = version + 1 where id = 9 and version = 7, rồi xem số dòng bị ảnh hưởng — bằng 0 thì trả 412.
- 428 Precondition Required: khi client PUT mà không gửi If-Match, server có thể từ chối bằng 428 để ép mọi client phải dùng conditional update, thay vì âm thầm cho ghi đè.
- Strong vs weak ETag: W/"v7" chỉ đảm bảo tương đương ngữ nghĩa, dùng cho cache; conditional update phải dùng strong ETag.
- If-None-Match là chiều đọc (trả 304 Not Modified để tiết kiệm băng thông) và If-None-Match: * khi PUT nghĩa là "chỉ tạo nếu chưa tồn tại" — chống tạo trùng.
So với pessimistic lock (khoá bản ghi khi mở form sửa), optimistic thắng ở chỗ không giữ khoá qua thời gian người dùng suy nghĩ; đổi lại client phải xử lý được 412 (hiển thị diff, hoặc reload rồi merge).