Най-вероятно попадате в състезателни условия . Когато стартирате функцията си 1000 пъти в бърза последователност в отделни транзакции , се случва нещо подобно:
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
...
До голяма степен пренаписана и опростена като 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;
Свързан въпрос с много повече обяснения:
Обяснете
-
Не изпълнявайте два отделни SQL оператора. Това е по-скъпо и разширява времевата рамка за условията на състезанието. Една
UPDATE
с подзаявка е много по-добре. -
Нямате нужда от PL/pgSQL за простата задача. Все още можете използвайте PL/pgSQL,
UPDATE
остава същата. -
Трябва да заключите избрания ред, за да се защитите от условията на състезание. Но не можете да направите това с агрегатната функция, която ръководите, защото по документация :
-
Удебелен акцент мой. За щастие можете да замените
min(id)
лесно с еквивалентнияORDER BY
/LIMIT 1
Дадох по-горе. Също така може да използва индекс. -
Ако масата е голяма, имате нужда от индекс на
id
поне. Ако приемем, чеid
вече е индексиран катоPRIMARY KEY
, това би помогнало. Но този допълнителен частичен многоколонен индекс вероятно ще помогне много повече :CREATE INDEX foo_idx ON table (id_type, id) WHERE id_used IS NULL;
Алтернативни решения
Препоръчителни ключалки Може да е най-добрият подход тук:
Или може да искате да заключите много редове наведнъж :