Boucle Nest indésirable vs jointure Hash dans PostgreSQL 9.6

13

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

Je l'ai fait VACUUM ANALYZEavant d'exécuter les requêtes, mais cela n'a pas aidé.

Je sais que ce n'est pas une bonne pratique set enable_nestloop = Falseet aucune 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.

Yury Lysogorskiy
la source
Un peu confus: pourquoi AND properties."TYPE_ID" IN (6);et non = 6;?
Vérace
2
@ Vérace tandis que = est plus largement utilisé, ils sont tous les deux rabotés de la même manière. Mon hypothèse est qu'il joue avec plus d'une valeur, ou un ORM est un peu paresseux.
Evan Carroll,

Réponses:

15

Ce qui se passe ici, c'est que la boucle imbriquée est loin d'un côté. Les boucles imbriquées fonctionnent très bien lorsqu'un côté est très petit, comme le retour d'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 de hachage (property_id = 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ésactivernested_loop

  • Vous pouvez ajouter un ou deux index propertiespour l'aider à potentiellement ignorer entièrement l'analyse séquentielle, ou mieux estimer le rendement.

    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;
  • Alternativement, vous pouvez déplacer le contenu des propriétés vers un CTE ou une sous-sélection avec OFFSET 0laquelle 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
    )
Evan Carroll
la source