Autant que je sache, il n'y a aucun moyen d'accomplir cela directement via le UPDATE
déclaration; la seule façon de garantir l'ordre des verrous est d'acquérir explicitement des verrous avec un SELECT ... ORDER BY ID FOR UPDATE
, par exemple :
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
)
Cela a l'inconvénient de répéter l'ID
recherche d'index sur les Balances
table. Dans votre exemple simple, vous pouvez éviter cette surcharge en récupérant l'adresse physique de la ligne (représentée par le ctid
colonne système
) pendant la requête de verrouillage, et l'utiliser pour piloter la 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
))
(Soyez prudent lorsque vous utilisez ctid
s, car les valeurs sont transitoires. Nous sommes en sécurité ici, car les verrous bloqueront tout changement.)
Malheureusement, le planificateur n'utilisera que le ctid
dans un ensemble restreint de cas (vous pouvez savoir si cela fonctionne en recherchant un nœud "Tid Scan" dans le EXPLAIN
production). Pour gérer des requêtes plus complexes dans une seule UPDATE
déclaration, par ex. si votre nouveau solde était retourné par some_function()
à côté de l'ID, vous devrez revenir à la recherche basée sur l'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
Si la surcharge de performances est un problème, vous devrez recourir à l'utilisation d'un curseur, qui ressemblera à ceci :
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
$$