PostgreSQL 9.6 中不受欢迎的 Nest Loop vs. Hash Join

Yur*_*kiy 17 postgresql performance join row-level-security postgresql-9.6 query-performance

我在 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)
Run Code Online (Sandbox Code Playgroud)

我为上面使用的表启用了行级安全性。

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
);
Run Code Online (Sandbox Code Playgroud)

任何想法或建议将不胜感激。

Eva*_*oll 21

这里发生的事情是嵌套循环在一侧。当一侧非常小时,例如返回一行时,嵌套循环非常有效。在您的查询中,规划器在这里摸索并估计 Hash Join 将仅返回一行。相反,该 Hash Join (property_id = id) 返回 1,338 行。这会强制在已有 3,444 行的嵌套循环的另一侧运行 1,338 个循环。当你只期待一个(这甚至不是一个“循环”)时,那真是太糟糕了。反正..

当我们向下移动时进一步检查表明,哈希联接确实被由此产生的估计所困扰,

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

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;
    
    Run Code Online (Sandbox Code Playgroud)
  • 或者,您可以将属性内容移动到OFFSET 0创建围栏的 CTE 或子选择。

    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
    )
    
    Run Code Online (Sandbox Code Playgroud)