Så vitt jag vet finns det inget sätt att åstadkomma detta direkt genom UPDATE
påstående; det enda sättet att garantera låsordning är att explicit skaffa lås med en SELECT ... ORDER BY ID FOR UPDATE
, t.ex.:
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
)
Detta har nackdelen med att upprepa ID
indexsökning på Balances
tabell. I ditt enkla exempel kan du undvika denna overhead genom att hämta den fysiska radadressen (representerad av ctid
systemkolumn
) under låsningsfrågan och använda den för att driva 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
))
(Var försiktig när du använder ctid
s, eftersom värdena är övergående. Vi är säkra här, eftersom låsen blockerar alla ändringar.)
Tyvärr kommer planeraren bara använda ctid
i en snäv uppsättning fall (du kan se om det fungerar genom att leta efter en "Tid Scan"-nod i EXPLAIN
produktion). För att hantera mer komplicerade frågor inom en enda UPDATE
uttalande, t.ex. om ditt nya saldo returnerades av some_function()
bredvid ID:t måste du gå tillbaka till den ID-baserade uppslagningen:
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
Om prestandaoverhead är ett problem, måste du använda en markör, som skulle se ut ungefär så här:
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
$$