Najprawdopodobniej napotykasz warunki wyścigowe . Gdy uruchomisz swoją funkcję 1000 razy w krótkim odstępie czasu w osobnych transakcjach , dzieje się coś takiego:
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
...
W dużej mierze przepisany i uproszczony jako funkcja SQL:
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;
Powiązane pytanie z dużo większym wyjaśnieniem:
Wyjaśnij
-
Nie uruchamiaj dwóch oddzielnych instrukcji SQL. To jest droższe i wydłuża ramy czasowe w warunkach wyścigu. Jedna
UPDATE
z podzapytanie jest znacznie lepsze. -
Nie potrzebujesz PL/pgSQL do prostego zadania. Nadal możesz użyj PL/pgSQL,
UPDATE
pozostaje bez zmian. -
Musisz zablokować wybrany rząd, aby obronić się przed warunkami wyścigu. Ale nie możesz tego zrobić za pomocą funkcji agregującej, którą kierujesz, ponieważ na dokumentację :
-
Moje odważne podkreślenie. Na szczęście możesz zastąpić
min(id)
łatwo z równoważnymORDER BY
/LIMIT 1
Podałem powyżej. Równie dobrze może używać indeksu. -
Jeśli stół jest duży, potrzebujesz indeks na
id
przynajmniej. Zakładając, żeid
jest już zaindeksowany jakoPRIMARY KEY
, to by pomogło. Ale ten dodatkowy częściowy indeks wielokolumnowy prawdopodobnie pomogłoby o wiele więcej :CREATE INDEX foo_idx ON table (id_type, id) WHERE id_used IS NULL;
Alternatywne rozwiązania
Zamki doradcze Może to być lepsze podejście tutaj:
Możesz też zablokować wiele wierszy naraz :