Hai thành viên cùng mở một ghi chú công việc. Người thứ nhất sửa nội dung rồi lưu. Người thứ hai vẫn nhìn bản cũ, sửa thêm một câu và bấm lưu sau đó. Nếu backend chỉ cập nhật theo ID, nội dung mới của người thứ nhất có thể bị thay thế mà không ai nhận ra.
Optimistic locking giải quyết tình huống này bằng một điều kiện: chỉ chấp nhận bản sửa nếu dữ liệu vẫn ở phiên bản mà người sửa đã đọc. Bài dùng PostgreSQL 17 làm mốc tài liệu, đối chiếu ngày 23/10/2026. Các câu SQL dành cho database thử riêng; ví dụ này chưa được chạy trên PostgreSQL trong lượt biên tập, nên các kết quả bên dưới là hành vi dự kiến để bạn kiểm tra.
Version phải đi cùng nội dung được đọc
Khi đọc ghi chú, backend trả cả nội dung và version. Client giữ version đó cùng bản nháp của biểu mẫu. Khi lưu, client gửi lại version đã đọc; backend kiểm tra nó trong cùng câu lệnh ghi, không dùng một lần SELECT kiểm tra rồi một câu UPDATE không điều kiện.
Ví dụ bảng nhỏ sau có một ghi chú thuộc workspace 7. workspace_id biểu diễn phạm vi mà backend đã kiểm tra quyền; không được tin giá trị workspace chỉ vì client gửi lên.
CREATE TABLE edit_demo (
id bigint PRIMARY KEY,
workspace_id bigint NOT NULL,
body text NOT NULL,
version bigint NOT NULL DEFAULT 1 CHECK (version > 0)
);
INSERT INTO edit_demo (id, workspace_id, body)
VALUES (1, 7, 'Ban dau');
SELECT id, body, version
FROM edit_demo
WHERE id = 1 AND workspace_id = 7;
Một cột updated_at vẫn hữu ích cho hiển thị thời gian nhưng dễ bị dùng sai làm mã phiên bản: độ chính xác, cách serialize và nguồn cập nhật thời gian có thể khác nhau. Cột số tăng rõ ràng giúp hợp đồng của ví dụ dễ kiểm tra hơn.
Gộp so sánh và cập nhật vào một câu lệnh
Giả sử cả hai người đều đọc được version 1. Lượt lưu đầu tiên thực hiện:
UPDATE edit_demo
SET body = 'Noi dung cua An', version = version + 1
WHERE id = 1 AND workspace_id = 7 AND version = 1
RETURNING id, body, version;
Nếu thành công, bản ghi trả về có version 2. Lượt lưu của người còn lại vẫn dùng điều kiện version 1 nên không cập nhật được hàng đó. Trong ứng dụng, thay các giá trị minh họa bằng bind parameter của driver; không ghép chuỗi SQL từ nội dung biểu mẫu.
Điểm quyết định là điều kiện version = ... nằm ngay trong UPDATE. Tài liệu PostgreSQL mô tả rằng ở Read Committed, khi gặp hàng được giao dịch khác cập nhật, câu lệnh chờ phù hợp rồi kiểm tra lại điều kiện trên phiên bản hàng đã cập nhật. Đây là cơ sở cho ví dụ một hàng này; không phải bảo đảm cho mọi bất biến liên quan nhiều bảng.
Không có hàng trả về cần được xử lý rõ
Backend phải đọc số hàng ảnh hưởng hoặc kết quả RETURNING. Không có hàng có thể do version cũ, bản ghi đã xóa, hoặc tài nguyên không còn nằm trong phạm vi mà người dùng được phép sửa. Đừng trả thành công chỉ vì driver không ném exception.
Sau khi kiểm tra phạm vi truy cập theo quy tắc của sản phẩm, trường hợp xung đột có thể được biểu diễn bằng phản hồi 409. Nếu thiết kế dùng If-Match và ETag như điều kiện HTTP, cần tuân theo semantics của precondition, thường có trường hợp 412. Không trộn hai hợp đồng mà không ghi rõ cho client.
Trên giao diện, giữ nguyên bản nháp của người dùng và cho họ xem bản mới nhất. Với đoạn văn dài, có thể hỗ trợ so sánh trước khi hợp nhất. Tự thay version cũ bằng version mới rồi gửi lại toàn bộ nội dung sẽ bỏ qua chính xung đột mà cơ chế này phát hiện.
Tự kiểm tra bằng hai phiên làm việc
Trong database thử, tạo bảng một lần rồi mở hai phiên cùng đọc version 1. Ở phiên A, bắt đầu transaction và chạy câu cập nhật nhưng chưa commit. Phiên B chạy cập nhật cùng ID và version cũ; kiểm tra việc chờ khóa. Commit A, rồi quan sát B không trả hàng phù hợp. Cuối cùng đọc lại dữ liệu: nội dung A phải còn nguyên và version bằng 2.
Thử thêm trường hợp A rollback: lúc đó B có thể tiếp tục cập nhật hàng còn version 1. Với Repeatable Read hoặc Serializable, phải xử lý cả lỗi serialization theo hợp đồng transaction; không suy rộng kết quả của Read Committed sang các isolation level khác.
Giới hạn và lỗi thường gặp
Mọi đường ghi đều phải tôn trọng version, kể cả script quản trị và tác vụ nền. Một đường ghi sửa nội dung nhưng quên tăng version sẽ làm client không phát hiện thay đổi. Các phép cập nhật nhiều hàng hoặc nhiều bảng còn cần transaction và ràng buộc phù hợp với bất biến nghiệp vụ.
Version không thay thế authorization. Nó cũng không ngăn gửi email hai lần hay tạo hai đơn hàng khi retry; đó là bài toán khác với chống mất cập nhật. Nếu chỉ cộng một bộ đếm, phép SET count = count + 1 có thể phù hợp hơn việc gửi lại giá trị tuyệt đối đã tính ở client.
Kết luận
Hãy xem version là lời hẹn giữa bản đọc và lần ghi tiếp theo. So sánh version trong câu cập nhật, kiểm tra kết quả thật và giữ bản nháp khi xung đột sẽ giúp người dùng giải quyết thay đổi mà không mất công việc đã làm.



