Nest Loop yang Tidak Diinginkan vs. Hash Bergabung di PostgreSQL 9.6

13

Saya mengalami masalah dengan perencanaan kueri PostgreSQL 9.6. Kueri saya terlihat seperti ini:

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)

Saya mengaktifkan Row-Level Security untuk tabel yang digunakan di atas.

Saya lakukan VACUUM ANALYZEsebelum menjalankan kueri, tetapi itu tidak membantu.

Saya tahu bahwa ini bukan praktik yang baik set enable_nestloop = False, dan opsi serupa lainnya untuk perencana. Tetapi bagaimana saya bisa "meyakinkan" perencana untuk menggunakan hash bergabung tanpa menonaktifkan loop bersarang?

Menulis ulang kueri adalah opsi.

Jika saya menjalankan kueri yang sama di bawah peran yang melewati RLS, maka itu dijalankan sangat cepat. Kebijakan keamanan tingkat baris terlihat seperti ini:

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

Setiap ide atau saran akan sangat dihargai.

Yury Lysogorskiy
sumber
Hanya sedikit bingung: mengapa harus AND properties."TYPE_ID" IN (6);dan tidak = 6;?
Vérace
2
@ Vérace sementara = lebih banyak digunakan secara luas, keduanya direncanakan dengan cara yang sama. Asumsi saya adalah bahwa dia bermain dengan lebih dari satu nilai, atau ORM sedang sedikit malas.
Evan Carroll

Jawaban:

15

Apa yang terjadi di sini adalah Nested Loop jauh di satu sisi. Nested Loops bekerja sangat baik ketika satu sisi sangat kecil, seperti mengembalikan satu baris. Dalam kueri Anda, perencana meraba-raba di sini dan memperkirakan bahwa Hash Join akan mengembalikan hanya satu baris. Alih-alih, Hash Join (property_id = id) mengembalikan 1.338 baris. Ini memaksa 1.338 loop untuk berjalan di sisi lain Nested Loop yang sudah memiliki 3.444 baris. Itu banyak hal ketika Anda hanya mengharapkan satu (yang bahkan tidak banyak "loop"). Pokoknya ..

Pemeriksaan lebih lanjut saat kami bergerak turun menunjukkan bahwa Hash Join benar-benar borked oleh estimasi yang timbul dari ini,

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

PostgreSQL mengharapkan itu mengembalikan satu baris. Tapi ternyata tidak. Dan, itu benar-benar masalah Anda. Jadi beberapa opsi di sini, yang tidak melibatkan mengeluarkan palu godam dan melumpuhkannested_loop

  • Anda dapat menambahkan satu atau dua indeks propertiesuntuk membantunya berpotensi melewati pemindaian seq sepenuhnya, atau memperkirakan pengembalian lebih baik.

    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;
  • Atau, Anda dapat memindahkan properti ke CTE atau subselect OFFSET 0yang membuat pagar.

    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
sumber