S největší pravděpodobností narazíte na závodní podmínky . Když svou funkci spustíte 1000krát rychle za sebou v samostatných transakcích , stane se něco takového:
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
...
Z velké části přepsáno a zjednodušeno jako funkce 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;
Související otázka s mnohem podrobnějším vysvětlením:
Vysvětlete
-
Nespouštějte dva samostatné příkazy SQL. To je dražší a prodlužuje to časový rámec pro podmínky závodu. Jedna
UPDATE
s poddotazem je mnohem lepší. -
Pro jednoduchý úkol nepotřebujete PL/pgSQL. Stále můžete použijte PL/pgSQL,
UPDATE
zůstává stejný. -
Chcete-li se bránit proti rasovým podmínkám, musíte vybraný řádek uzamknout. Ale nemůžete to udělat s agregační funkcí, kterou vedete, protože podle dokumentace :
-
Odvážný důraz můj. Naštěstí můžete nahradit
min(id)
snadno pomocí ekvivalentníhoORDER BY
/LIMIT 1
Poskytl jsem výše. Stejně dobře může používat index. -
Pokud je stůl velký, potřebujete index na
id
alespoň. Za předpokladu, žeid
je již indexován jakoPRIMARY KEY
, to by pomohlo. Ale tento další částečný vícesloupcový index pravděpodobně pomůže mnohem více :CREATE INDEX foo_idx ON table (id_type, id) WHERE id_used IS NULL;
Alternativní řešení
Poradní zámky Zde může být lepší přístup:
Nebo můžete chtít uzamknout mnoho řádků najednou :