Indesejável Nest Loop vs. Hash Join no PostgreSQL 9.6

13

Estou com um problema no planejamento de consultas do PostgreSQL 9.6. Minha consulta é assim:

SET role plain_user;

SELECT properties.*
FROM properties
JOIN entries_properties
  ON properties.id = entries_properties.property_id
JOIN structures
  ON structures.id = entries_properties.entry_id 
WHERE structures."STRUKTURBERICHT" != ''
  AND properties."COMPOSITION" LIKE 'Mo%'
  AND (
    properties."NAME" LIKE '%VASP-ase-preopt%'
    OR properties."CALCULATOR_ID" IN (7,22,25)
  )
AND properties."TYPE_ID" IN (6)

Eu tenho a segurança em nível de linha ativada para as tabelas usadas acima.

  • com set enable_nestloop = True, o planejador de consultas executa a junção de loop aninhado com um tempo total de execução de aproximadamente 37 segundos: https://explain.depesz.com/s/59BR

  • com set enable_nestloop = False, o método Hash Join é usado e o tempo de consulta é de cerca de 0,3 segundos: https://explain.depesz.com/s/PG8E

Eu fiz VACUUM ANALYZEantes de executar as consultas, mas não ajudou.

Sei que não é uma boa prática set enable_nestloop = Falsee outras opções semelhantes para o planejador. Mas como "convencer" o planejador a usar junções de hash sem desativar os loops aninhados?

Reescrever a consulta é uma opção.

Se eu executar a mesma consulta em uma função que ignora o RLS, ela será executada muito rapidamente. A política de segurança em nível de linha é semelhante a esta:

CREATE POLICY properties_select
ON properties
FOR SELECT
USING (
  (
    properties.ouid = get_current_user_id()
    AND properties.ur
  )
  OR (
    properties.ogid in (select get_current_groups_id())
    AND properties.gr
  )
  OR properties.ar
);

Todas as idéias ou sugestões serão muito apreciadas.

Yury Lysogorskiy
fonte
Um pouco confuso: por que ter AND properties."TYPE_ID" IN (6);e não = 6;?
Vérace 25/10
2
@ Vérace enquanto = é usado mais amplamente, ambos são planejados da mesma maneira. Minha suposição é que ele está jogando com mais de um valor, ou um ORM está sendo um pouco preguiçoso.
Evan Carroll

Respostas:

15

O que está acontecendo aqui é que o Nested Loop está muito distante de um lado. Loops aninhados funcionam muito bem quando um lado é muito pequeno, como retornar uma linha. Na sua consulta, o planejador se atrapalha aqui e estima que uma Hash Join retornará apenas uma linha. Em vez disso, essa Hash Join (property_id = id) retorna 1.338 linhas. Isso força 1.338 loops a serem executados no outro lado do loop aninhado, que já possui 3.444 linhas. Isso é demais quando você está esperando apenas um (o que não é nem um "loop"). Enfim ..

Um exame mais aprofundado à medida que avançamos mostra que o Hash Join é realmente influenciado pelas estimativas decorrentes disso,

Filter: (((properties."COMPOSITION")::text ~~ 'Mo%'::text) AND (((properties."NAME")::text ~~ '%VASP-ase-preopt%'::text) OR (properties."CALCULATOR_ID" = ANY ('{7,22,25}'::integer[]))))

O PostgreSQL espera que retorne uma linha. Mas isso não acontece. E esse é realmente o seu problema. Portanto, algumas opções aqui, que não envolvem tirar uma marreta e desativarnested_loop

  • Você pode adicionar um índice ou dois para propertiesajudá-lo a ignorar a verificação seq, ou estimar melhor o retorno.

    CREATE INDEX ON properties USING ( "TYPE_ID", "CALCULATOR_ID" );
    -- the gist_trgm_ops may or may not be needed depending on selectivity of above.
    CREATE INDEX ON properties USING GIST (
      "COMPOSITION" gist_trgm_ops,
      "NAME"        gist_trgm_ops
    );
    ANALYZE properties;
    
  • Como alternativa, você pode mover o material das propriedades para um CTE ou subselecionar com o OFFSET 0qual cria uma cerca.

    WITH t AS (
      SELECT *
      FROM properties.
      WHERE "COMPOSITION" LIKE 'Mo%'
      AND (
        "NAME" LIKE '%VASP-ase-preopt%'
        OR "CALCULATOR_ID" IN (7,22,25)
      )
      AND "TYPE_ID" IN (6)
    )
    SELECT * FROM structures
    JOIN t ON (
      structures.id = entries_properties.entry_id
    )
    
Evan Carroll
fonte