Pokud vím, neexistuje způsob, jak toho dosáhnout přímo prostřednictvím UPDATE
tvrzení; jediný způsob, jak zaručit pořadí zámků, je explicitně získat zámky pomocí SELECT ... ORDER BY ID FOR UPDATE
, např.:
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
)
To má nevýhodu v opakování ID
vyhledávání indexu na Balances
stůl. Ve vašem jednoduchém příkladu se této režii můžete vyhnout načtením adresy fyzického řádku (reprezentované ctid
systémový sloupec
) během zamykacího dotazu a pomocí toho řídit 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
))
(Při používání ctid
buďte opatrní s, protože hodnoty jsou přechodné. Jsme zde v bezpečí, protože zámky zablokují jakékoli změny.)
Plánovač bohužel použije pouze ctid
v úzké sadě případů (jestli to funguje, zjistíte tak, že v EXPLAIN
najdete uzel „Tid Scan“ výstup). Pro zpracování složitějších dotazů v rámci jednoho UPDATE
prohlášení, např. pokud byl váš nový zůstatek vrácen pomocí some_function()
vedle ID se budete muset vrátit k vyhledávání založenému na ID:
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
Pokud je problém s režií výkonu, budete se muset uchýlit k použití kurzoru, který by vypadal asi takto:
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
$$