Soweit ich weiß, gibt es keine Möglichkeit, dies direkt über das UPDATE zu erreichen Aussage; Die einzige Möglichkeit, die Sperrreihenfolge zu garantieren, besteht darin, Sperren explizit mit einem SELECT ... ORDER BY ID FOR UPDATE zu erwerben , z. B.:
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
)
Dies hat den Nachteil, dass die ID wiederholt wird Indexsuche auf den Balances Tisch. In Ihrem einfachen Beispiel können Sie diesen Aufwand vermeiden, indem Sie die physische Zeilenadresse abrufen (dargestellt durch ctid Systemspalte
) während der Sperrabfrage, und verwenden Sie diese, um das UPDATE zu steuern :
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
))
(Seien Sie vorsichtig bei der Verwendung von ctid s, da die Werte transient sind. Wir sind hier sicher, da die Sperren alle Änderungen blockieren.)
Leider verwendet der Planer nur die ctid in einer engen Gruppe von Fällen (Sie können feststellen, ob es funktioniert, indem Sie nach einem "Tid Scan"-Knoten in EXPLAIN suchen Ausgang). Um kompliziertere Abfragen innerhalb eines einzigen UPDATE zu behandeln Aussage, z. wenn Ihr neuer Kontostand von some_function() zurückgegeben wurde Neben der ID müssen Sie auf die ID-basierte Suche zurückgreifen:
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
Wenn der Leistungsaufwand ein Problem darstellt, müssen Sie auf die Verwendung eines Cursors zurückgreifen, der ungefähr so aussehen würde:
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
$$