Per quanto ne so, non c'è modo di farlo direttamente tramite UPDATE
dichiarazione; l'unico modo per garantire l'ordine dei blocchi è acquisire esplicitamente i blocchi con un SELECT ... ORDER BY ID FOR UPDATE
, ad esempio:
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
)
Questo ha lo svantaggio di ripetere l'ID
ricerca dell'indice su Balances
tavolo. Nel tuo semplice esempio, puoi evitare questo sovraccarico recuperando l'indirizzo della riga fisica (rappresentato da ctid
colonna di sistema
) durante la query di blocco e utilizzandolo per guidare l'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
))
(Fai attenzione quando usi ctid
s, poiché i valori sono transitori. Siamo al sicuro qui, poiché i blocchi bloccheranno qualsiasi modifica.)
Sfortunatamente, il pianificatore utilizzerà solo il ctid
in un ristretto insieme di casi (puoi sapere se funziona cercando un nodo "Tid Scan" in EXPLAIN
produzione). Per gestire query più complicate all'interno di un singolo UPDATE
dichiarazione, ad es. se il tuo nuovo saldo è stato restituito da some_function()
oltre all'ID, dovrai tornare alla ricerca basata sull'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
Se l'overhead delle prestazioni è un problema, dovresti ricorrere all'utilizzo di un cursore, che sarebbe simile a questo:
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
$$