Muito provavelmente você está enfrentando condições de corrida . Quando você executa sua função 1000 vezes em rápida sucessão em transações separadas , acontece algo assim:
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
...
Em grande parte reescrito e simplificado como função 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;
Pergunta relacionada com muito mais explicação:
Explicar
-
Não execute duas instruções SQL separadas. Isso é mais caro e amplia o prazo para as condições de corrida. UmUPDATE
com uma subconsulta é muito melhor.
-
Você não precisa de PL/pgSQL para uma tarefa simples. Você ainda pode use PL/pgSQL, oUPDATE
continua o mesmo.
-
Você precisa bloquear a linha selecionada para se defender contra as condições de corrida. Mas você não pode fazer isso com a função agregada que você dirige porque, por documentação :
-
Minha ênfase em negrito. Felizmente, você pode substituirmin(id)
facilmente com o equivalenteORDER BY
/LIMIT 1
forneci acima. Pode usar um índice tão bem.
-
Se a mesa for grande, você precisa um índice emid
pelo menos. Supondo queid
já está indexado comoPRIMARY KEY
, isso ajudaria. Mas este índice multicoluna parcial adicional provavelmente ajudaria muito mais :
CREATE INDEX foo_idx ON table (id_type, id) WHERE id_used IS NULL;
Soluções alternativas
Bloqueios de aviso Pode ser a abordagem superior aqui:
Ou você pode bloquear muitas linhas de uma vez :