web-dev-qa-db-fra.com

Boucle Nest indésirable vs jointure par hachage dans PostgreSQL 9.6

J'ai un problème avec la planification des requêtes de PostgreSQL 9.6. Ma requête ressemble à ceci:

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)

J'ai la sécurité au niveau des lignes activée pour les tables utilisées ci-dessus.

  • avec set enable_nestloop = True, le planificateur de requêtes exécute la jonction de boucles imbriquées avec un temps d'exécution total d'environ 37 secondes: https://explain.depesz.com/s/59BR

  • avec set enable_nestloop = False, la méthode Hash Join est utilisée et le temps de requête est d'environ 0,3 sec: https://explain.depesz.com/s/PG8E

J'ai fait VACUUM ANALYZE avant d'exécuter les requêtes, mais cela n'a pas aidé.

Je sais que ce n'est pas une bonne pratique de set enable_nestloop = False, et toute autre option similaire pour le planificateur. Mais comment "convaincre" le planificateur d'utiliser des jointures de hachage sans désactiver les boucles imbriquées?

La réécriture de la requête est une option.

Si j'exécute la même requête sous un rôle qui contourne RLS, alors elle est exécutée très rapidement. La politique de sécurité au niveau des lignes ressemble à ceci:

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
);

Toutes les idées ou suggestions seraient grandement appréciées.

13
Yury Lysogorskiy

Ce qui se passe ici, c'est que la boucle imbriquée est loin d'un côté. Les boucles imbriquées fonctionnent vraiment bien quand un côté est très petit, comme retourner une ligne. Dans votre requête, le planificateur cherche ici et estime qu'une jointure par hachage ne renverra qu'une seule ligne. Au lieu de cela, cette jointure par hachage (id_propriété = id) renvoie 1 338 lignes. Cela force 1 338 boucles à s'exécuter de l'autre côté de la boucle imbriquée qui compte déjà 3 444 lignes. C'est un sacré lot quand vous n'en attendez qu'un (ce qui n'est même pas vraiment une "boucle"). Quoi qu'il en soit ..

Un examen plus approfondi à mesure que nous avançons montre que la jointure par hachage est vraiment altérée par les estimations qui en découlent,

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

PostgreSQL s'attend à ce que cela renvoie une ligne. Mais ce n'est pas le cas. Et c'est vraiment votre problème. Donc, certaines options ici, qui n'impliquent pas de retirer un marteau et de désactiver nested_loop

  • Vous pouvez ajouter un ou deux index à properties pour l'aider à ignorer complètement l'analyse seq, ou mieux estimer le retour.

    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;
    
  • Vous pouvez également déplacer les propriétés vers un CTE ou sous-sélectionner avec OFFSET 0 qui crée une clôture.

    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
    )
    
15
Evan Carroll