Mest sandsynligt løber du ind i løbsforhold . Når du kører din funktion 1000 gange hurtigt efter hinanden i særskilte transaktioner , sker der noget som dette:
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
...
Stort set omskrevet og forenklet 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;
Relateret spørgsmål med meget mere forklaring:
Forklar
-
Kør ikke to separate SQL-sætninger. Det er dyrere og udvider tidsrammen for løbsforhold. Én
UPDATEmed en underforespørgsel er meget bedre. -
Du behøver ikke PL/pgSQL til den simple opgave. Du kan stadig brug PL/pgSQL,
UPDATEforbliver den samme. -
Du skal låse den valgte række for at forsvare dig mod løbsforhold. Men du kan ikke gøre dette med den samlede funktion, du leder, fordi pr. dokumentation :
-
Fed fremhævelse min. Heldigvis kan du erstatte
min(id)nemt med den tilsvarendeORDER BY/LIMIT 1Jeg gav ovenfor. Kan lige så godt bruge et indeks. -
Hvis bordet er stort, skal du bruge et indeks på
idi det mindste. Forudsat atider allerede indekseret somPRIMARY KEY, det ville hjælpe. Men dette yderligere delvise flerkolonneindeks ville nok hjælpe meget mere :CREATE INDEX foo_idx ON table (id_type, id) WHERE id_used IS NULL;
Alternative løsninger
Rådgivende låse Kan være den overlegne tilgang her:
Eller du vil måske låse mange rækker på én gang :