Khóa trong Postgres cho kết hợp CẬP NHẬT / CHERTN


11

Tôi có hai bàn. Một là bảng nhật ký; một mã khác chứa, về cơ bản, mã phiếu giảm giá chỉ có thể được sử dụng một lần.

Người dùng cần có khả năng đổi phiếu giảm giá, sẽ chèn một hàng vào bảng nhật ký và đánh dấu phiếu giảm giá đã sử dụng (bằng cách cập nhật usedcột thành true).

Đương nhiên, có một vấn đề an ninh / điều kiện chủng tộc rõ ràng ở đây.

Tôi đã từng làm những điều tương tự trong quá khứ trong thế giới của myQuery. Trong thế giới đó, tôi sẽ khóa cả hai bảng trên toàn cầu, làm logic an toàn với kiến ​​thức rằng điều này chỉ có thể xảy ra một lần tại một thời điểm và sau đó mở khóa các bảng sau khi tôi hoàn thành.

Có cách nào tốt hơn trong Postgres để làm điều này? Cụ thể, tôi lo ngại rằng khóa là toàn cầu, nhưng không phải vậy - tôi thực sự chỉ cần đảm bảo không ai khác đang cố nhập mã cụ thể đó, vì vậy có lẽ một số khóa cấp hàng sẽ hoạt động?

Câu trả lời:


14

Tôi đã nghe nói về các vấn đề tương tranh như thế trong MySQL trước đây. Không như vậy trong Postgres.

Khóa cấp độ tích hợp trong READ COMMITTEDmức cô lập giao dịch mặc định là đủ.

Tôi đề xuất một câu lệnh với CTE sửa đổi dữ liệu (thứ mà MySQL cũng không có) bởi vì nó thuận tiện để chuyển trực tiếp các giá trị từ bảng này sang bảng khác (nếu bạn cần điều đó). Nếu bạn không cần bất cứ điều gì từ các couponbảng mà bạn có thể sử dụng một giao dịch với riêng UPDATEINSERTbáo cáo chỉ là tốt.

WITH upd AS (
   UPDATE coupon
   SET    used = true
   WHERE  coupon_id = 123
   AND    NOT used
   RETURNING coupon_id, other_column
   )
INSERT INTO log (coupon_id, other_column)
SELECT coupon_id, other_column FROM upd;

Nó là một điều hiếm hoi mà nhiều hơn một giao dịch cố gắng mua lại cùng một phiếu giảm giá. Họ có một số duy nhất, phải không? Nhiều hơn một giao dịch cố gắng tại cùng một thời điểm sẽ hiếm hơn nhiều. (Có thể là một lỗi ứng dụng hoặc ai đó đang cố gắng chơi trò chơi hệ thống?)

Là như nó có thể, UPDATEchỉ thành công cho chính xác một giao dịch, không có vấn đề gì. Một UPDATEcó được một khóa cấp hàng trên mỗi hàng mục tiêu trước khi cập nhật. Nếu một giao dịch đồng thời cố gắng vào UPDATEcùng một hàng, nó sẽ thấy khóa trên hàng đó và đợi cho đến khi giao dịch chặn kết thúc ( ROLLBACKhoặc COMMIT), sau đó trở thành đầu tiên trong hàng đợi khóa:

  • Nếu cam kết, kiểm tra lại điều kiện. Nếu vẫn còn NOT used, khóa hàng và tiến hành. Khác UPDATEbây giờ không tìm thấy hàng đủ điều kiện và không làm , không trả lại hàng, vì vậy INSERTcũng không có gì.

  • Nếu cuộn lại, khóa hàng và tiến hành.

Không có tiềm năng cho một điều kiện cuộc đua .

Không có khả năng xảy ra bế tắc trừ khi bạn đặt nhiều ghi vào cùng một giao dịch hoặc nếu không thì khóa nhiều hàng hơn chỉ một hàng.

Việc INSERTnày là vô tư. Nếu, do một số sai lầm coupon_idđã có trong logbảng (và bạn có một ràng buộc ĐỘC ĐÁO hoặc PK log.coupon_id), toàn bộ giao dịch sẽ được khôi phục sau một vi phạm duy nhất. Sẽ chỉ ra một trạng thái bất hợp pháp trong DB của bạn. Nếu tuyên bố trên là cách duy nhất để ghi vào logbảng, điều đó sẽ không bao giờ xảy ra.


Đây thực sự là một điều hiếm khi có nhiều hơn một giao dịch cố gắng đổi mã giống nhau, nhưng sự nghi ngờ của bạn là đúng ở chỗ điều này sẽ chỉ có khi ai đó đang cố gắng chơi trò chơi hệ thống. Cảm ơn rất nhiều vì điều này - CTE là một lợi thế lớn cho tôi khi chuyển sang Postgres nhưng tôi đã không nhận ra rằng khóa ngầm sẽ đủ tốt cho việc này.
Rob Miller
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.