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

Obtenha o filho mais recente por pai da tabela grande - a consulta é muito lenta


O ponto principal é provavelmente você JOIN e GROUP sobre tudo apenas para obter max(created) . Obtenha este valor separadamente.

Você mencionou todos os índices necessários aqui:em report_rank.created e nas chaves estrangeiras. Você está indo bem aí. (Se você estiver interessado em algo melhor do que "tudo bem", continue lendo !)

O LEFT JOIN report_site será forçado a um simples JOIN pelo WHERE cláusula. Eu substituí um simples JOIN . Eu também simplifiquei muito sua sintaxe.

Atualizado em julho de 2015 com consultas mais simples e rápidas e funções mais inteligentes.

Solução para várias linhas


report_rank.created é não é exclusivo e você quer todos as últimas linhas.
Usando a função de janela rank() em uma subconsulta.
SELECT r.id, r.keyword_id, r.site_id
     , r.rank, r.url, r.competition
     , r.source, r.country, r.created  -- same as "max"
FROM  (
   SELECT *, rank() OVER (ORDER BY created DESC NULLS LAST) AS rnk
   FROM   report_rank r
   WHERE  EXISTS (
      SELECT *
      FROM   report_site    s
      JOIN   report_profile p ON p.site_id = s.id
      JOIN   crm_client     c ON c.id      = p.client_id
      JOIN   auth_user      u ON u.id      = c.user_id
      WHERE  s.id = r.site_id
      AND    u.is_active
      AND    c.is_deleted = FALSE
      )
   ) sub
WHERE  rnk = 1;

Por que DESC NULLS LAST ?

Solução para uma linha


Se report_rank.created é único ou você está satisfeito com qualquer 1 linha com max(created) :
SELECT id, keyword_id, site_id
     , rank, url, competition
     , source, country, created  -- same as "max"
FROM   report_rank r
WHERE  EXISTS (
    SELECT 1
    FROM   report_site    s
    JOIN   report_profile p ON p.site_id = s.id
    JOIN   crm_client     c ON c.id      = p.client_id
    JOIN   auth_user      u ON u.id      = c.user_id
    WHERE  s.id = r.site_id
    AND    u.is_active
    AND    c.is_deleted = FALSE
   )
-- AND  r.created > f_report_rank_cap()
ORDER  BY r.created DESC NULLS LAST
LIMIT  1;

Deve ser mais rápido, ainda. Mais opções:

Velocidade máxima com índice parcial ajustado dinamicamente


Você deve ter notado a parte comentada na última consulta:
AND  r.created > f_report_rank_cap()

Você mencionou 50 milhões. linhas, isso é muito. Aqui está uma maneira de acelerar as coisas:
  • Crie um IMMUTABLE simples função retornando um carimbo de data/hora que é garantido ser mais antigo que as linhas de interesse, sendo o mais jovem possível.
  • Crie um índice parcial somente em linhas mais novas - com base nesta função.
  • Use um WHERE condição nas consultas que correspondem à condição do índice.
  • Crie outra função que atualize esses objetos para a linha mais recente com DDL dinâmico. (Menos uma margem segura caso as linhas mais recentes sejam excluídas / desativadas - se isso puder acontecer)
  • Invoque esta função secundária fora do horário de funcionamento com um mínimo de atividade simultânea por cronjob ou sob demanda. Quantas vezes você quiser, não pode fazer mal, só precisa de um pequeno cadeado exclusivo na mesa.

Aqui está uma demonstração de trabalho completa .
@erikcw, você terá que ativar a parte comentada conforme as instruções abaixo.
CREATE TABLE report_rank(created timestamp);
INSERT INTO report_rank VALUES ('2011-11-11 11:11'),(now());

-- initial function
CREATE OR REPLACE FUNCTION f_report_rank_cap()
  RETURNS timestamp LANGUAGE sql COST 1 IMMUTABLE AS
$y$SELECT timestamp '-infinity'$y$;  -- or as high as you can safely bet.

-- initial index; 1st run indexes whole tbl if starting with '-infinity'
CREATE INDEX report_rank_recent_idx ON report_rank (created DESC NULLS LAST)
WHERE  created > f_report_rank_cap();

-- function to update function & reindex
CREATE OR REPLACE FUNCTION f_report_rank_set_cap()
  RETURNS void AS
$func$
DECLARE
   _secure_margin CONSTANT interval := interval '1 day';  -- adjust to your case
   _cap timestamp;  -- exclude older rows than this from partial index
BEGIN
   SELECT max(created) - _secure_margin
   FROM   report_rank
   WHERE  created > f_report_rank_cap() + _secure_margin
   /*  not needed for the demo; @erikcw needs to activate this
   AND    EXISTS (
     SELECT *
     FROM   report_site    s
     JOIN   report_profile p ON p.site_id = s.id
     JOIN   crm_client     c ON c.id      = p.client_id
     JOIN   auth_user      u ON u.id      = c.user_id
     WHERE  s.id = r.site_id
     AND    u.is_active
     AND    c.is_deleted = FALSE)
   */
   INTO   _cap;

   IF FOUND THEN
     -- recreate function
     EXECUTE format('
     CREATE OR REPLACE FUNCTION f_report_rank_cap()
       RETURNS timestamp LANGUAGE sql IMMUTABLE AS
     $y$SELECT %L::timestamp$y$', _cap);

     -- reindex
     REINDEX INDEX report_rank_recent_idx;
   END IF;
END
$func$  LANGUAGE plpgsql;

COMMENT ON FUNCTION f_report_rank_set_cap()
IS 'Dynamically recreate function f_report_rank_cap()
    and reindex partial index on report_rank.';

Ligar:
SELECT f_report_rank_set_cap();

Ver:
SELECT f_report_rank_cap();

Descomente a cláusula AND r.created > f_report_rank_cap() na consulta acima e observe a diferença. Verifique se o índice é usado com EXPLAIN ANALYZE .

O manual sobre simultaneidade e REINDEX :