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
UPDATEmet een subquery is veel beter. -
U hebt geen PL/pgSQL nodig voor de eenvoudige taak. Je kunt nog gebruik PL/pgSQL, de
UPDATEblijft 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 1heb ik hierboven gegeven. Kan net zo goed een index gebruiken. -
Als de tafel groot is, heb je nodig een index op
idtenminste. Ervan uitgaande datidis 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 :