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
UPDATE
med en underforespørgsel er meget bedre. -
Du behøver ikke PL/pgSQL til den simple opgave. Du kan stadig brug PL/pgSQL,
UPDATE
forbliver 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 1
Jeg gav ovenfor. Kan lige så godt bruge et indeks. -
Hvis bordet er stort, skal du bruge et indeks på
id
i det mindste. Forudsat atid
er 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 :