Até onde eu sei, não há como fazer isso diretamente através do
UPDATE
declaração; a única maneira de garantir a ordem de bloqueio é adquirir bloqueios explicitamente com um SELECT ... ORDER BY ID FOR UPDATE
, por exemplo.: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
)
Isso tem a desvantagem de repetir o
ID
pesquisa de índice em Balances
tabela. Em seu exemplo simples, você pode evitar essa sobrecarga buscando o endereço da linha física (representado pelo ctid
coluna do sistema
) durante a consulta de bloqueio e usando isso para conduzir o 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
))
(Tenha cuidado ao usar
ctid
s, pois os valores são transitórios. Estamos seguros aqui, pois os bloqueios bloquearão qualquer alteração.) Infelizmente, o planejador utilizará apenas o
ctid
em um conjunto restrito de casos (você pode saber se está funcionando procurando por um nó "Tid Scan" no EXPLAIN
resultado). Para lidar com consultas mais complicadas em um único UPDATE
declaração, por exemplo se seu novo saldo estava sendo retornado por some_function()
junto com o ID, você precisará retornar à pesquisa baseada em 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 a sobrecarga de desempenho for um problema, você precisará recorrer ao uso de um cursor, que seria algo assim:
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
$$