O ile mi wiadomo, nie ma możliwości wykonania tego bezpośrednio przez UPDATE
oświadczenie; jedynym sposobem zagwarantowania kolejności blokad jest jawne uzyskanie blokad za pomocą SELECT ... ORDER BY ID FOR UPDATE
, np.:
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
)
Ma to wadę polegającą na powtarzaniu ID
wyszukiwanie indeksu Balances
stół. W swoim prostym przykładzie możesz uniknąć tego obciążenia, pobierając fizyczny adres wiersza (reprezentowany przez ctid
kolumna systemowa
) podczas zapytania blokującego i używając go do kierowania 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
))
(Uważaj używając ctid
s, ponieważ wartości są przejściowe. Tutaj jesteśmy bezpieczni, ponieważ blokady zablokują wszelkie zmiany).
Niestety planista użyje tylko ctid
w wąskim zestawie przypadków (możesz stwierdzić, czy działa, szukając węzła „Tid Scan” w EXPLAIN
wyjście). Aby obsłużyć bardziej skomplikowane zapytania w ramach jednej UPDATE
oświadczenie, np. jeśli Twoje nowe saldo zostało zwrócone przez some_function()
obok identyfikatora musisz wrócić do wyszukiwania opartego na identyfikatorze:
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
Jeśli problemem jest narzut wydajności, musisz skorzystać z kursora, który wygląda mniej więcej tak:
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
$$