Trong PostgreSQL 9.1 trở lên bạn có thể thực hiện việc này chỉ với một câu lệnh sử dụng CTE sửa đổi dữ liệu . Điều này thường ít xảy ra lỗi hơn. Nó giảm thiểu khung thời gian giữa hai DELETE trong đó điều kiện cuộc đua có thể dẫn đến kết quả đáng ngạc nhiên với các hoạt động đồng thời:
WITH del_child AS (
DELETE FROM child
WHERE child_id = 1
RETURNING parent_id, child_id
)
DELETE FROM parent p
USING del_child x
WHERE p.parent_id = x.parent_id
AND NOT EXISTS (
SELECT 1
FROM child c
WHERE c.parent_id = x.parent_id
AND c.child_id <> x.child_id -- !
);
SQL Fiddle.
Đứa trẻ bị xóa trong mọi trường hợp. Tôi trích dẫn sách hướng dẫn:
Các câu lệnh sửa đổi dữ liệu trong
WITH
được thực thi chính xác một lần và luôn hoàn thành , không phụ thuộc vào việc truy vấn chính có đọc tất cả (hoặc thực sự là bất kỳ) đầu ra của chúng hay không. Lưu ý rằng điều này khác với quy tắc choSELECT
trongWITH
:như đã nêu trong phần trước, việc thực thiSELECT
chỉ được thực hiện khi truy vấn chính và kết quả đầu ra của nó.
Cấp độ gốc chỉ bị xóa nếu nó không có khác trẻ em.
Lưu ý điều kiện cuối cùng. Trái ngược với những gì người ta có thể mong đợi, điều này là cần thiết, vì:
Các câu lệnh con trong
WITH
được thực thi đồng thời với mỗi otherand với truy vấn chính. Do đó, khi sử dụng các câu lệnh sửa đổi dữ liệu trongWITH
, thứ tự mà các bản cập nhật được chỉ định thực sự xảy ra là không thể đoán trước. Tất cả các câu lệnh được thực thi bằng samesnapshot (xem Chương 13), vì vậy chúng không thể "nhìn thấy" các hiệu ứng của nhau trên các bảng đích.
Nhấn mạnh đậm của tôi.
Tôi đã sử dụng tên cột parent_id
thay cho id
không mô tả .
Loại bỏ tình trạng chủng tộc
Để loại bỏ các điều kiện chủng tộc có thể xảy ra, tôi đã đề cập ở trên hoàn toàn , khóa hàng mẹ đầu tiên . Tất nhiên, tất cả các hoạt động tương tự phải tuân theo cùng một quy trình để làm cho nó hoạt động.
WITH lock_parent AS (
SELECT p.parent_id, c.child_id
FROM child c
JOIN parent p ON p.parent_id = c.parent_id
WHERE c.child_id = 12 -- provide child_id here once
FOR NO KEY UPDATE -- locks parent row.
)
, del_child AS (
DELETE FROM child c
USING lock_parent l
WHERE c.child_id = l.child_id
)
DELETE FROM parent p
USING lock_parent l
WHERE p.parent_id = l.parent_id
AND NOT EXISTS (
SELECT 1
FROM child c
WHERE c.parent_id = l.parent_id
AND c.child_id <> l.child_id -- !
);
Cách này chỉ một giao dịch tại một thời điểm có thể khóa cùng một cha mẹ. Vì vậy, không thể xảy ra trường hợp nhiều giao dịch xóa các con của cùng một phụ huynh, vẫn nhìn thấy các trẻ em khác và tha cho phụ huynh đó, trong khi tất cả các trẻ em đã biến mất sau đó. (Cập nhật trên các cột không phải khóa vẫn được phép với FOR NO KEY UPDATE
.)
Nếu những trường hợp như vậy không bao giờ xảy ra hoặc bạn có thể sống chung với nó (hiếm khi) xảy ra - truy vấn đầu tiên sẽ rẻ hơn. Mặt khác, đây là đường dẫn an toàn.
FOR NO KEY UPDATE
đã được giới thiệu với Postgres 9.4. Chi tiết trong sách hướng dẫn. Trong các phiên bản cũ hơn, hãy sử dụng khóa mạnh hơn FOR UPDATE
thay vào đó.