Hoogstwaarschijnlijk loop je tegen race-omstandigheden . Wanneer u uw functie 1000 keer snel achter elkaar uitvoert in afzonderlijke transacties , gebeurt er iets als dit:
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
...
Grotendeels herschreven en vereenvoudigd als SQL-functie:
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;
Gerelateerde vraag met veel meer uitleg:
Uitleggen
-
Voer geen twee afzonderlijke SQL-instructies uit. Dat is duurder en verlengt het tijdsbestek voor race-omstandigheden. Eén
UPDATE
met een subquery is veel beter. -
U hebt geen PL/pgSQL nodig voor de eenvoudige taak. Je kunt nog gebruik PL/pgSQL, de
UPDATE
blijft hetzelfde. -
Je moet de geselecteerde rij vergrendelen om je te verdedigen tegen race-omstandigheden. Maar u kunt dit niet doen met de aggregatiefunctie die u aanstuurt, omdat, per documentatie :
-
Vetgedrukte nadruk van mij. Gelukkig kun je
min(id)
. vervangen gemakkelijk met de equivalenteORDER BY
/LIMIT 1
heb ik hierboven gegeven. Kan net zo goed een index gebruiken. -
Als de tafel groot is, heb je nodig een index op
id
tenminste. Ervan uitgaande datid
is al geïndexeerd alsPRIMARY KEY
, dat zou helpen. Maar deze aanvullende gedeeltelijke index met meerdere kolommen zou waarschijnlijk veel meer helpen :CREATE INDEX foo_idx ON table (id_type, id) WHERE id_used IS NULL;
Alternatieve oplossingen
Adviessloten Misschien is dit de superieure benadering:
Of misschien wil je veel rijen tegelijk :