PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Postgres UPDATE mit ORDER BY, wie geht das?

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