PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Função que demora uma eternidade para ser executada para um grande número de registros


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. Um UPDATE com uma subconsulta é muito melhor.

  • Você não precisa de PL/pgSQL para uma tarefa simples. Você ainda pode use PL/pgSQL, o UPDATE 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 substituir min(id) facilmente com o equivalente ORDER BY / LIMIT 1 forneci acima. Pode usar um índice tão bem.

  • Se a mesa for grande, você precisa um índice em id pelo menos. Supondo que id já está indexado como PRIMARY 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 :