Theo như tôi biết, không có cách nào để thực hiện điều này trực tiếp thông qua UPDATE
bản tường trình; cách duy nhất để đảm bảo thứ tự khóa là có được khóa một cách rõ ràng bằng SELECT ... ORDER BY ID FOR UPDATE
, ví dụ:
UPDATE Balances
SET Balance = 0
WHERE ID IN (
SELECT ID FROM Balances
WHERE ID IN (SELECT ID FROM some_function())
ORDER BY ID
FOR UPDATE
)
Điều này có mặt trái của việc lặp lại ID
tra cứu chỉ mục trên Balances
bàn. Trong ví dụ đơn giản của bạn, bạn có thể tránh chi phí này bằng cách tìm nạp địa chỉ hàng vật lý (được đại diện bởi ctid
cột hệ thống
) trong khi truy vấn khóa và sử dụng điều đó để thúc đẩy UPDATE
:
UPDATE Balances
SET Balance = 0
WHERE ctid = ANY(ARRAY(
SELECT ctid FROM Balances
WHERE ID IN (SELECT ID FROM some_function())
ORDER BY ID
FOR UPDATE
))
(Hãy cẩn thận khi sử dụng ctid
s, vì các giá trị là tạm thời. Chúng tôi ở đây an toàn, vì ổ khóa sẽ chặn mọi thay đổi.)
Thật không may, người lập kế hoạch sẽ chỉ sử dụng ctid
trong một số trường hợp hẹp (bạn có thể biết nó có hoạt động hay không bằng cách tìm nút "Quét Tid" trong EXPLAIN
đầu ra). Để xử lý các truy vấn phức tạp hơn trong một UPDATE
duy nhất tuyên bố, ví dụ:nếu số dư mới của bạn được trả lại bởi some_function()
cùng với ID, bạn sẽ cần quay lại tra cứu dựa trên ID:
UPDATE Balances
SET Balance = Locks.NewBalance
FROM (
SELECT Balances.ID, some_function.NewBalance
FROM Balances
JOIN some_function() ON some_function.ID = Balances.ID
ORDER BY Balances.ID
FOR UPDATE
) Locks
WHERE Balances.ID = Locks.ID
Nếu chi phí hiệu suất là một vấn đề, bạn cần phải sử dụng con trỏ, con trỏ sẽ trông giống như sau:
DO $$
DECLARE
c CURSOR FOR
SELECT Balances.ID, some_function.NewBalance
FROM Balances
JOIN some_function() ON some_function.ID = Balances.ID
ORDER BY Balances.ID
FOR UPDATE;
BEGIN
FOR row IN c LOOP
UPDATE Balances
SET Balance = row.NewBalance
WHERE CURRENT OF c;
END LOOP;
END
$$