Niepożądana pętla zagnieżdżenia a łączenie przez mieszanie w PostgreSQL 9.6

13

Mam problem z planowaniem zapytań PostgreSQL 9.6. Moje zapytanie wygląda następująco:

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)

Mam włączone zabezpieczenia na poziomie wiersza dla wyżej używanych tabel.

  • za pomocą set enable_nestloop = True, narzędzie do planowania zapytań uruchamia łączenie zagnieżdżonej pętli z łącznym czasem działania około 37 sekund: https://explain.depesz.com/s/59BR

  • z set enable_nestloop = False, używana jest metoda łączenia Hash, a czas zapytania wynosi około 0,3 s: https://explain.depesz.com/s/PG8E

Zrobiłem to VACUUM ANALYZEprzed uruchomieniem zapytań, ale to nie pomogło.

Wiem, że nie jest to dobra praktyka set enable_nestloop = Falsei inne podobne opcje dla planisty. Ale jak mogę „przekonać” planistę do korzystania z połączeń mieszających bez wyłączania zagnieżdżonych pętli?

Przepisanie zapytania jest opcją.

Jeśli uruchomię to samo zapytanie w roli, która omija RLS, jest ono wykonywane bardzo szybko. Polityka bezpieczeństwa na poziomie wiersza wygląda następująco:

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

Wszelkie pomysły i sugestie będą mile widziane.

Jurij Łysogorski
źródło
Trochę zmieszany: dlaczego tak jest, AND properties."TYPE_ID" IN (6);a nie = 6;?
Vérace
2
@ Vérace, podczas gdy = jest szerzej stosowane, oba są planowane w ten sam sposób. Zakładam, że gra z więcej niż jedną wartością, albo ORM jest trochę leniwy.
Evan Carroll

Odpowiedzi:

15

To, co się tutaj dzieje, to zagnieżdżona pętla z jednej strony. Zagnieżdżone pętle działają naprawdę dobrze, gdy jedna strona jest bardzo mała, na przykład zwracając jeden rząd. W zapytaniu planista grzebiąc tutaj, szacuje, że funkcja łączenia mieszającego zwróci tylko jeden wiersz. Zamiast tego funkcja Hash Join (property_id = id) zwraca 1338 wierszy. Wymusza to uruchomienie 1338 pętli po drugiej stronie zagnieżdżonej pętli, która ma już 3444 wiersze. To bardzo dużo, gdy oczekujesz tylko jednego (który nie jest nawet „pętlą”). W każdym razie ..

Dalsze badanie, gdy schodzimy w dół, pokazuje, że łączenie Hash jest naprawdę zniekształcone przez szacunki wynikające z tego

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

PostgreSQL oczekuje, że zwróci jeden wiersz. Ale tak nie jest. I to naprawdę twój problem. Oto niektóre opcje, które nie wymagają wyjmowania młota i wyłączanianested_loop

  • Możesz dodać indeks lub dwa, aby propertiespomóc potencjalnie całkowicie pominąć skanowanie seq lub lepiej oszacować zwrot.

    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;
    
  • Alternatywnie możesz przenieść właściwości właściwości do CTE lub podselekcji, z OFFSET 0którą tworzy ogrodzenie.

    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
źródło