Molto probabilmente ti stai imbattendo in condizioni di gara . Quando esegui la tua funzione 1000 volte in rapida successione in transazioni separate , succede qualcosa del genere:
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
...
In gran parte riscritto e semplificato come funzione 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;
Domanda correlata con molte più spiegazioni:
Spiega
-
Non eseguire due istruzioni SQL separate. Questo è più costoso e amplia il periodo di tempo per le condizioni di gara. Un
UPDATE
con una sottoquery è molto meglio. -
Non è necessario PL/pgSQL per l'attività semplice. Puoi ancora puoi usa PL/pgSQL, il
UPDATE
rimane lo stesso. -
Devi bloccare la riga selezionata per difenderti dalle condizioni di gara. Ma non puoi farlo con la funzione di aggregazione che dirigi perché, per documentazione :
-
Enfasi in grassetto mio. Fortunatamente, puoi sostituire
min(id)
facilmente con l'equivalenteORDER BY
/LIMIT 1
Ho fornito sopra. Può usare anche un indice. -
Se il tavolo è grande, è necessario un indice su
id
almeno. Supponendo cheid
è già indicizzato comePRIMARY KEY
, questo aiuterebbe. Ma questo ulteriore indice parziale multicolonna probabilmente aiuterebbe molto di più :CREATE INDEX foo_idx ON table (id_type, id) WHERE id_used IS NULL;
Soluzioni alternative
Blocchi di avviso Potrebbe essere l'approccio superiore qui:
Oppure potresti voler bloccare più righe contemporaneamente :