Med största sannolikhet stöter du på tävlingsförhållanden . När du kör din funktion 1000 gånger i snabb följd i separata transaktioner , något liknande detta händer:
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
...
Till stor del omskriven och förenklad som SQL-funktion:
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;
Relaterad fråga med mycket mer förklaring:
Förklara
-
Kör inte två separata SQL-satser. Det är dyrare och breddar tidsramen för tävlingsförhållanden. En
UPDATE
med en underfråga är mycket bättre. -
Du behöver inte PL/pgSQL för den enkla uppgiften. Du kan fortfarande använd PL/pgSQL,
UPDATE
förblir densamma. -
Du måste låsa den valda raden för att försvara dig mot tävlingsförhållanden. Men du kan inte göra detta med den aggregatfunktion du leder eftersom, per dokumentation :
-
Djärv betoning min. Som tur är kan du ersätta
min(id)
enkelt med motsvarandeORDER BY
/LIMIT 1
Jag angav ovan. Kan använda ett index lika bra. -
Om bordet är stort behöver ett index på
id
minst. Förutsatt attid
är redan indexerad somPRIMARY KEY
, det skulle hjälpa. Men detta ytterligare partiella flerkolumnindex skulle förmodligen hjälpa mycket mer :CREATE INDEX foo_idx ON table (id_type, id) WHERE id_used IS NULL;
Alternativa lösningar
Rådgivande lås Kan vara det överlägsna tillvägagångssättet här:
Eller så kanske du vill låsa många rader samtidigt :