通过条件过滤优化昂贵的连接/子查询

wvh*_*wvh 7 postgresql join

这是一个关于在 Postgresql(9.5 或 9.6)中短路昂贵的 JOIN 或子查询的问题。我也有兴趣了解人们通常如何解决先检查后执行的问题。

我正在编写很多只应有条件地返回结果的查询,例如(网络)用户是否拥有记录或记录是否被修改。我试图阻止在 Postgresql 中构建昂贵的视图和多个来回查询来检查应用程序本身中的条件,因此我尝试编写首先选择正确记录并显示哪些条件失败的查询,并且只执行查看条件是否通过。

例如,这会在返回之前检查(应用程序)用户是否拥有记录:

SELECT is_owner, is_newer, json 
FROM (
     SELECT id, owner = '053bffbc-c41e-dad4-853b-ea91fc42ea18' "is_owner"
          , modified >= created "is_newer" 
     FROM datasets 
     WHERE id = '056e4eed-ee63-2add-e981-0c86b8b6a66f'
) cond
LEFT JOIN LATERAL (
     SELECT id 
     FROM datasets 
     WHERE is_owner and is_newer
) authed
    ON cond.id = authed.id
LEFT JOIN LATERAL (
     SELECT json 
     FROM view_dataset 
     WHERE id = authed.id
) dataset
    ON true;
Run Code Online (Sandbox Code Playgroud)

结果(是所有者):

is_owner | is_newer | json
t          t          {...}
Run Code Online (Sandbox Code Playgroud)

和负面结果(不是所有者):

is_owner | is_newer | json
f          t          NULL
Run Code Online (Sandbox Code Playgroud)

因此,应用程序知道要返回哪个错误,但如果条件未通过,我们就不必构建或解析视图。

但是,EXPLAIN ANALYZE 显示Postgresql 仍然在最后一个 LEFT LATERAL JOIN 中执行视图查询,即使中间 JOIN 没有任何结果,而且我无法将其短路以防止(昂贵的)view_dataset SELECT 运行. 如果我设置jsonnull查询跳过除第一个 SELECT 之外的所有内容;但是如果它被设置为来自最后一个查询的值,它将始终执行所有 SELECT,所以我猜查询规划器认为它必须json在顶部 SELECT 查询中获得该字段的结果并且不会短路 JOIN .

我想知道是否可以强制 Postgresql 删除昂贵的视图查询。

我也试过一个 CTE,它似乎跳过了 JOIN 查询:

WITH cond as (
    SELECT id, owner = '053bffbc-c41e-dad4-853b-ea91fc42ea18' "is_owner", modified >= created "is_newer" FROM datasets WHERE id = '056e4eed-ee63-2add-e981-0c86b8b6a66f'
)
SELECT cond.id, cond.is_owner, cond.is_newer, json FROM
    (SELECT id FROM cond WHERE cond.is_owner and cond.is_newer) filtered
    LEFT JOIN LATERAL
    (SELECT id, json from view_dataset) dataset
    USING (id)
    RIGHT JOIN cond
    USING(id);
Run Code Online (Sandbox Code Playgroud)

...但是这个查询和变体至少慢了 2 倍。

所以我的问题是如何根据条件通过短路 JOIN 或子查询来最大化性能;我也很想知道是否有人有其他想法如何实现一些先检查然后执行的模式,例如检查记录所有权。

Len*_*art 1

authed根本不知道为什么需要。什么是:

SELECT is_owner, is_newer, json 
FROM (
     SELECT id, owner = '053bffbc-c41e-dad4-853b-ea91fc42ea18' "is_owner"
          , modified >= created "is_newer" 
     FROM datasets 
     WHERE id = '056e4eed-ee63-2add-e981-0c86b8b6a66f'
) cond
LEFT JOIN LATERAL (
     SELECT json 
     FROM view_dataset 
     WHERE id = cond.id
) dataset
    ON is_owner and is_newer;
Run Code Online (Sandbox Code Playgroud)

由你?我也同意a_horse_with_no_name的评论。在特殊情况下,LATERAL 可以为将谓词推送到基表提供巨大帮助,但它只是一个变相的子查询,因此在大多数情况下,执行普通联接更有意义。也请尝试:

SELECT is_owner, is_newer, json 
FROM (
     SELECT id, owner = '053bffbc-c41e-dad4-853b-ea91fc42ea18' "is_owner"
          , modified >= created "is_newer" 
     FROM datasets 
     WHERE id = '056e4eed-ee63-2add-e981-0c86b8b6a66f'
) cond
LEFT JOIN view_dataset wd
    ON wd.id = cond.id 
   AND cond.is_owner 
   AND cond.is_newer;
Run Code Online (Sandbox Code Playgroud)

编辑。表值函数

CREATE FUNCTION get_view_dataset(int,bool) 
    RETURNS setof view_dataset AS '
        SELECT * 
        FROM view_dataset wd
        WHERE wd.id = $1 
          AND $2;
    ' LANGUAGE SQL;
Run Code Online (Sandbox Code Playgroud)

然后在查询中使用该函数:

SELECT is_owner, is_newer, json 
FROM (
     SELECT id, owner = '053bffbc-c41e-dad4-853b-ea91fc42ea18' "is_owner"
          , modified >= created "is_newer" 
     FROM datasets 
     WHERE id = '056e4eed-ee63-2add-e981-0c86b8b6a66f'
) cond
LEFT JOIN get_view_dataset(cond.id, cond.is_owner AND cond.is_newer);
Run Code Online (Sandbox Code Playgroud)

全部未经测试。