Nest Loop indeseable vs.Hash Únete en PostgreSQL 9.6

13

Tengo un problema con la planificación de consultas PostgreSQL 9.6. Mi consulta se ve así:

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)

Tengo habilitada la seguridad de nivel de fila para las tablas utilizadas anteriormente.

  • con set enable_nestloop = True, el planificador de consultas ejecuta Nested Loop uniéndose con un tiempo de ejecución total de aproximadamente 37 segundos: https://explain.depesz.com/s/59BR

  • con set enable_nestloop = False, se utiliza el método Hash Join y el tiempo de consulta es de aproximadamente 0.3 segundos: https://explain.depesz.com/s/PG8E

Lo hice VACUUM ANALYZEantes de ejecutar las consultas, pero no ayudó.

Sé que no es una buena práctica set enable_nestloop = False, y cualquier otra opción similar para el planificador. Pero, ¿cómo podría "convencer" al planificador de que use combinaciones hash sin deshabilitar los bucles anidados?

Reescribir la consulta es una opción.

Si ejecuto la misma consulta bajo un rol que omite RLS, entonces se ejecuta muy rápido. La política de seguridad de nivel de fila se ve así:

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

Cualquier idea o sugerencia sería muy apreciada.

Yury Lysogorskiy
fuente
Solo un poco confundido: ¿por qué tener AND properties."TYPE_ID" IN (6);y no = 6;?
Vérace
2
@ Vérace while = se usa más ampliamente, ambos se planean de la misma manera. Mi suposición es que está jugando con más de un valor, o un ORM está siendo un poco vago.
Evan Carroll

Respuestas:

15

Lo que está sucediendo aquí es que el Nested Loop está muy alejado de un lado. Los bucles anidados funcionan realmente bien cuando un lado es muy pequeño, como devolver una fila. En su consulta, el planificador se equivoca aquí y estima que una Hash Join devolverá solo una fila. En cambio, esa Hash Join (property_id = id) devuelve 1,338 filas. Esto obliga a 1,338 bucles a correr en el otro lado del bucle anidado que ya tiene 3,444 filas. Eso es un montón cuando solo esperas uno (que ni siquiera es un "bucle"). De todos modos ..

Un examen más detallado a medida que avanzamos muestra que Hash Join está realmente alterado por las estimaciones derivadas de esto,

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

PostgreSQL espera que eso devuelva una fila. Pero no lo hace. Y ese es realmente tu problema. Entonces, algunas opciones aquí, que no implican sacar un martillo y deshabilitarnested_loop

  • Puede agregar un índice o dos para propertiesayudarlo a omitir por completo el análisis de secuencia, o estimar mejor el rendimiento.

    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;
  • Alternativamente, puede mover las cosas de propiedades a un CTE o subseleccionar con las OFFSET 0cuales crea una 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
fuente