Hasta donde yo sé, no hay forma de lograr esto directamente a través de UPDATE
declaración; la única forma de garantizar el orden de bloqueo es adquirir bloqueos explícitamente con SELECT ... ORDER BY ID FOR UPDATE
, por ejemplo:
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
)
Esto tiene la desventaja de repetir el ID
búsqueda de índice en los Balances
mesa. En su ejemplo simple, puede evitar esta sobrecarga obteniendo la dirección de la fila física (representada por ctid
columna del sistema
) durante la consulta de bloqueo, y usándolo para impulsar la 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
))
(Tenga cuidado al usar ctid
s, ya que los valores son transitorios. Estamos a salvo aquí, ya que las cerraduras bloquearán cualquier cambio).
Desafortunadamente, el planificador solo utilizará el ctid
en un conjunto limitado de casos (puede saber si está funcionando buscando un nodo "Tid Scan" en EXPLAIN
producción). Para manejar consultas más complicadas dentro de una sola UPDATE
declaración, por ej. si su nuevo saldo estaba siendo devuelto por some_function()
junto con el ID, deberá recurrir a la búsqueda basada en 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
Si la sobrecarga de rendimiento es un problema, deberá recurrir al uso de un cursor, que se vería así:
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
$$