Voor zover ik weet, is er geen manier om dit rechtstreeks te bereiken via de UPDATE
uitspraak; de enige manier om de vergrendelingsvolgorde te garanderen, is door expliciet sloten te verwerven met een SELECT ... ORDER BY ID FOR UPDATE
, bijv.:
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
)
Dit heeft het nadeel van het herhalen van de ID
index opzoeken op de Balances
tafel. In uw eenvoudige voorbeeld kunt u deze overhead vermijden door het fysieke rijadres op te halen (weergegeven door de ctid
systeemkolom
) tijdens de vergrendelingsquery, en die gebruiken om de UPDATE
. aan te sturen :
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
))
(Wees voorzichtig bij het gebruik van ctid
s, aangezien de waarden van voorbijgaande aard zijn. We zijn hier veilig, omdat de sloten alle wijzigingen blokkeren.)
Helaas gebruikt de planner alleen de ctid
in een beperkt aantal gevallen (u kunt zien of het werkt door te zoeken naar een "Tid Scan"-knooppunt in de EXPLAIN
uitgang). Om meer gecompliceerde zoekopdrachten af te handelen binnen één UPDATE
verklaring, bijv. als uw nieuwe saldo werd geretourneerd door some_function()
naast de ID moet je terugvallen op de ID-gebaseerde lookup:
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
Als de prestatieoverhead een probleem is, moet u een cursor gebruiken, die er ongeveer zo uitziet:
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
$$