Lo más probable es que te encuentres con condiciones de carrera . Cuando ejecuta su función 1000 veces en rápida sucesión en transacciones separadas , sucede algo como esto:
T1 T2 T3 ...
SELECT max(id) -- id 1
SELECT max(id) -- id 1
SELECT max(id) -- id 1
...
Row id 1 locked, wait ...
Row id 1 locked, wait ...
UPDATE id 1
...
COMMIT
Wake up, UPDATE id 1 again!
COMMIT
Wake up, UPDATE id 1 again!
COMMIT
...
En gran parte reescrito y simplificado como función SQL:
CREATE OR REPLACE FUNCTION get_result(val1 text, val2 text)
RETURNS text AS
$func$
UPDATE table t
SET id_used = 'Y'
, col1 = val1
, id_used_date = now()
FROM (
SELECT id
FROM table
WHERE id_used IS NULL
AND id_type = val2
ORDER BY id
LIMIT 1
FOR UPDATE -- lock to avoid race condition! see below ...
) t1
WHERE t.id_type = val2
-- AND t.id_used IS NULL -- repeat condition (not if row is locked)
AND t.id = t1.id
RETURNING id;
$func$ LANGUAGE sql;
Pregunta relacionada con mucha más explicación:
Explicar
-
No ejecute dos declaraciones SQL separadas. Eso es más caro y amplía el marco de tiempo para las condiciones de carrera. Una
UPDATE
con una subconsulta es mucho mejor. -
No necesita PL/pgSQL para la tarea simple. Todavía puedes use PL/pgSQL, el
UPDATE
permanece igual. -
Debe bloquear la fila seleccionada para defenderse de las condiciones de carrera. Pero no puede hacer esto con la función agregada que dirige porque, por documentación :
-
Énfasis en negrita mío. Afortunadamente, puedes reemplazar
min(id)
fácilmente con el equivalenteORDER BY
/LIMIT 1
proporcioné arriba. También puede usar un índice. -
Si la mesa es grande, necesita un índice en
id
al menos. Asumiendo queid
ya está indexado comoPRIMARY KEY
, Eso podría ayudar. Pero este índice parcial de varias columnas adicional probablemente ayudaría mucho más :CREATE INDEX foo_idx ON table (id_type, id) WHERE id_used IS NULL;
Soluciones alternativas
Bloqueos de aviso Puede ser el enfoque superior aquí:
O es posible que desee bloquear muchas filas a la vez :