Доколкото знам, няма начин да се постигне това директно чрез UPDATE
изявление; единственият начин да се гарантира редът на заключването е изрично придобиване на заключвания с SELECT ... ORDER BY ID FOR UPDATE
, напр.:
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
)
Това има обратната страна на повтарянето на ID
търсене на индекс на Balances
маса. Във вашия прост пример можете да избегнете това натоварване, като извлечете адреса на физическия ред (представен от ctid
системна колона
) по време на заявката за заключване и използване на това за задвижване на 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
))
(Бъдете внимателни, когато използвате ctid
s, тъй като стойностите са преходни. Тук сме в безопасност, тъй като ключалките ще блокират всички промени.)
За съжаление плановият ще използва само ctid
в тесен набор от случаи (можете да разберете дали работи, като потърсите възел „Tid Scan“ в EXPLAIN
изход). За обработка на по-сложни заявки в рамките на една UPDATE
изявление, напр. ако новият ви баланс е бил върнат от some_function()
заедно с ID, ще трябва да се върнете към търсенето, базирано на 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
Ако режийните разходи за производителност са проблем, ще трябва да прибягвате до използване на курсор, който ще изглежда по следния начин:
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
$$