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
$$