Så vidt jeg ved, er der ingen måde at opnå dette direkte gennem UPDATE
udmelding; den eneste måde at garantere låserækkefølge på er eksplicit at erhverve låse med en SELECT ... ORDER BY ID FOR UPDATE
, f.eks.:
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
)
Dette har ulempen ved at gentage ID
indeksopslag på Balances
bord. I dit simple eksempel kan du undgå denne overhead ved at hente den fysiske rækkeadresse (repræsenteret ved ctid
systemkolonne
) under låseforespørgslen og bruge den til at køre 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
))
(Vær forsigtig, når du bruger ctid
s, da værdierne er forbigående. Vi er i sikkerhed her, da låsene vil blokere eventuelle ændringer.)
Desværre vil planlæggeren kun bruge ctid
i et snævert sæt tilfælde (du kan se, om det virker, ved at kigge efter en "Tid Scan"-node i EXPLAIN
produktion). At håndtere mere komplicerede forespørgsler inden for en enkelt UPDATE
erklæring, f.eks. hvis din nye saldo blev returneret af some_function()
ved siden af ID'et skal du vende tilbage til det ID-baserede opslag:
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
Hvis ydelsesomkostningerne er et problem, skal du ty til at bruge en markør, som ville se nogenlunde sådan ud:
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
$$