Tránh vi phạm duy nhất trong giao dịch nguyên tử


15

Có thể tạo giao dịch nguyên tử trong PostgreSQL không?

Xem xét tôi có thể loại bảng với các hàng này:

id|name
--|---------
1 |'tablets'
2 |'phones'

Và tên cột có ràng buộc duy nhất.

Nếu tôi cố gắng:

BEGIN;
update "category" set name = 'phones' where id = 1;
update "category" set name = 'tablets' where id = 2;
COMMIT;

Tôi nhận được:

ERROR:  duplicate key value violates unique constraint "category_name_key"
DETAIL:  Key (name)=(tablets) already exists.

Câu trả lời:


24

Ngoài những gì @Craig cung cấp (và sửa một số lỗi):

Hiệu quả Postgres 9.4 , UNIQUE, PRIMARY KEYEXCLUDEhạn chế được kiểm tra ngay lập tức sau khi mỗi hàng khi được xác định NOT DEFERRABLE. Điều này khác với các loại NOT DEFERRABLEràng buộc khác (hiện tại chỉ REFERENCES(khóa ngoại)) được kiểm tra sau mỗi câu lệnh . Chúng tôi đã giải quyết tất cả những điều này theo câu hỏi liên quan này trên SO:

Sẽ không đủ để một ràng buộc UNIQUE(hoặc PRIMARY KEYhoặc EXCLUDE) DEFERRABLElàm cho mã được trình bày của bạn với nhiều câu lệnh hoạt động.

Và bạn không thể sử dụng ALTER TABLE ... ALTER CONSTRAINTcho mục đích này. Mỗi tài liệu:

ALTER CONSTRAINT

Biểu mẫu này thay đổi các thuộc tính của một ràng buộc đã được tạo trước đó. Hiện tại chỉ có các ràng buộc khóa ngoại có thể được thay đổi .

Nhấn mạnh đậm của tôi. Sử dụng thay thế:

ALTER TABLE t
   DROP CONSTRAINT category_name_key
 , ADD  CONSTRAINT category_name_key UNIQUE(name) DEFERRABLE;

Thả và thêm các ràng buộc trở lại trong một câu lệnh để không có cửa sổ thời gian cho bất kỳ ai lẻn vào các hàng vi phạm. Đối với các bảng lớn, sẽ rất hấp dẫn khi bảo tồn chỉ mục duy nhất tiềm ẩn bằng cách nào đó, bởi vì rất tốn kém để xóa và tạo lại nó. Than ôi, điều đó dường như là không thể với các công cụ tiêu chuẩn (nếu bạn có giải pháp cho điều đó, xin vui lòng cho chúng tôi biết!):

Đối với một tuyên bố duy nhất làm cho ràng buộc có thể được bảo vệ là đủ:

UPDATE category c
SET    name = c_old.name
FROM   category c_old
WHERE  c.id     IN (1,2)
AND    c_old.id IN (1,2)
AND    c.id <> c_old.id;

Một truy vấn với CTEs cũng là một đơn tuyên bố:

WITH x AS (
    UPDATE category SET name = 'phones' WHERE id = 1
    )
UPDATE category SET name = 'tablets' WHERE id = 2;

Tuy nhiên , đối với mã của bạn có nhiều câu lệnh, bạn (ngoài ra) cần thực sự trì hoãn ràng buộc - hoặc xác định nó là INITIALLY DEFERREDEither thường đắt hơn so với ở trên. Nhưng nó có thể không dễ dàng khả thi để đóng gói mọi thứ vào một tuyên bố.

BEGIN;
SET CONSTRAINTS category_name_key DEFERRED;
UPDATE category SET name = 'phones'  WHERE id = 1;
UPDATE category SET name = 'tablets' WHERE id = 2;
COMMIT;

Mặc dù vậy, hãy chú ý đến một giới hạn liên quan đến các FOREIGN KEYràng buộc. Mỗi tài liệu:

Các cột được tham chiếu phải là các cột của ràng buộc khóa chính hoặc duy nhất không thể bảo vệ trong bảng được tham chiếu.

Vì vậy, bạn không thể có cả hai cùng một lúc.


13

Theo tôi hiểu, vấn đề của bạn ở đây là ràng buộc được kiểm tra sau mỗi câu lệnh, nhưng bạn muốn nó được kiểm tra ở cuối giao dịch, vì vậy nó so sánh trạng thái trước với trạng thái sau, bỏ qua trạng thái trung gian.

Nếu vậy, điều đó là có thể với một ràng buộc có thể bảo vệ .

Xem SET CONSTRAINTSvà các DEFERRABLEràng buộc như tài liệu trong CREATE TABLE.

Lưu ý rằng các ràng buộc hoãn lại có chi phí - hệ thống phải giữ một danh sách chúng để kiểm tra tại thời điểm cam kết, vì vậy chúng không tốt cho các giao dịch tạo ra các thay đổi lớn. Họ cũng chậm hơn để kiểm tra.

Vì vậy, tôi nghĩ rằng bạn có thể muốn:

ALTER TABLE mytable ALTER CONSTRAINT category_name_key DEFERRABLE;

Lưu ý rằng dường như có một giới hạn về ALTER TABLEcài đặt các ràng buộc đối với DEFERRABLE; bạn có thể phải thay thế DROPADDgiới hạn lại.

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.