En la respuesta relacionada a la que te refieres:
- ACTUALIZACIÓN de Postgres... LÍMITE 1
El objetivo es bloquear uno fila a la vez. Esto funciona bien con o sin bloqueos de aviso, porque no hay posibilidad de bloqueo. - siempre que no intente bloquear más filas en la misma transacción.
Su ejemplo es diferente en el sentido de que desea bloquear 3000 filas a la vez . Hay es posibilidad de interbloqueo, excepto si todas las operaciones de escritura simultáneas bloquean filas en el mismo orden coherente. Por documentación:
Por lo general, la mejor defensa contra los interbloqueos es evitarlos asegurándose de que todas las aplicaciones que utilizan una base de datos adquieran bloqueos en varios objetos en un orden coherente.
Implemente eso con ORDER BY en su subconsulta.
UPDATE cargo_item item
SET job_id = 'SOME_UUID', job_ts = now()
FROM (
SELECT id
FROM cargo_item
WHERE state='NEW' AND job_id is null
ORDER BY id
LIMIT 3000
FOR UPDATE
) sub
WHERE item.id = sub.id;
Esto es seguro y confiable, siempre y cuando todos las transacciones adquieren bloqueos en el mismo orden y no se esperan actualizaciones simultáneas de las columnas de orden. (Lea el cuadro amarillo de "PRECAUCIÓN" al final de este capítulo en el manual). Por lo tanto, esto debería ser seguro en su caso, ya que no actualizará el id
columna.
Efectivamente, solo un cliente a la vez puede manipular filas de esta manera. Las transacciones simultáneas intentarían bloquear las mismas filas (bloqueadas) y esperar a que finalice la primera transacción.
Bloqueos de aviso son útiles si tiene muchas o muy largas transacciones simultáneas (no parece que las tenga). Con solo unos pocos, será más económico en general usar la consulta anterior y hacer que las transacciones simultáneas esperen su turno.
Todo en uno ACTUALIZACIÓN
Parece que el acceso concurrente no es un problema per se en su configuración. La concurrencia es un problema creado por su solución actual.
En su lugar, hazlo todo en una sola UPDATE
. Asigna lotes de n
números (3000 en el ejemplo) a cada UUID y actualice todo a la vez. Debería ser el más rápido.
UPDATE cargo_item c
SET job_id = u.uuid_col
, job_ts = now()
FROM (
SELECT row_number() OVER () AS rn, uuid_col
FROM uuid_tbl WHERE <some_criteria> -- or see below
) u
JOIN (
SELECT (row_number() OVER () / 3000) + 1 AS rn, item.id
FROM cargo_item
WHERE state = 'NEW' AND job_id IS NULL
FOR UPDATE -- just to be sure
) c2 USING (rn)
WHERE c2.item_id = c.item_id;
Puntos principales
-
La división entera trunca. Obtiene 1 para las primeras 3000 filas, 2 para las siguientes 3000 filas. etc.
-
Elijo filas arbitrariamente, podrías aplicar
ORDER BY
en la ventana derow_number()
para asignar ciertas filas. -
Si no tiene una tabla de UUID para enviar (
uuid_tbl
), use unVALUES
expresión para suministrarlos. Ejemplo. -
Obtiene lotes de 3000 filas. Al último lote le faltarán 3000 si no encuentra un múltiplo de 3000 para asignar.