Нежелательный цикл Nest vs. Hash Join в PostgreSQL 9.6

13

У меня проблема с планированием запросов PostgreSQL 9.6. Мой запрос выглядит так:

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)

У меня включена защита на уровне строк для вышеуказанных таблиц.

  • с set enable_nestloop = Trueпомощью планировщика запросов выполняется объединение Nested Loop с общим временем выполнения около 37 секунд: https://explain.depesz.com/s/59BR

  • При set enable_nestloop = Falseиспользовании метода Hash Join время запроса составляет около 0,3 с: https://explain.depesz.com/s/PG8E.

Я сделал VACUUM ANALYZEдо запуска запросов, но это не помогло.

Я знаю, что это не очень хорошая практика set enable_nestloop = False, и любые другие подобные варианты для планировщика. Но как я мог «убедить» планировщика использовать хеш-соединения, не отключая вложенные циклы?

Переписать запрос - вариант.

Если я выполняю тот же запрос под ролью, которая обходит RLS, то он выполняется очень быстро. Политика безопасности на уровне строк выглядит следующим образом:

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

Любые идеи или предложения будут с благодарностью.

Юрий Лысогорский
источник
Просто немного запутался: почему есть AND properties."TYPE_ID" IN (6);и нет = 6;?
Vérace
2
@ Vérace while = более широко используется, они оба планируются одинаково. Я предполагаю, что он играет с более чем одним значением, или ORM немного ленив.
Эван Кэрролл

Ответы:

15

То, что здесь происходит, это то, что Nested Loop находится на одной стороне. Вложенные циклы работают очень хорошо, когда одна сторона очень мала, например, возвращает один ряд. В вашем запросе планировщик возится здесь и оценивает, что Hash Join вернет только одну строку. Вместо этого Hash Join (property_id = id) возвращает 1338 строк. Это приводит к запуску 1338 циклов на другой стороне вложенного цикла, который уже содержит 3444 строки. Это отличная партия, когда вы ожидаете только одного (что даже не является «петлей»). Anywayy ..

Дальнейшее изучение по мере того, как мы движемся вниз, показывает, что Hash Join действительно не соответствует оценкам, вытекающим из этого,

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

PostgreSQL ожидает, что он вернет одну строку. Но это не так. И это действительно ваша проблема. Так что некоторые варианты здесь, которые не включают в себя вывоз кувалды и отключениеnested_loop

  • Вы можете добавить индекс или два, чтобы propertiesпомочь ему полностью пропустить сканирование seq или лучше оценить результат.

    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;
  • В качестве альтернативы, вы можете переместить материал свойств в CTE или подвыбрать, с помощью OFFSET 0которого создается забор.

    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
    )
Эван Кэрролл
источник