web-dev-qa-db-ja.com

条件付きでフィルタリングすることによる高価な結合/サブクエリの最適化

これは、Postgresql(9.5または9.6)の高価なJOINまたはサブクエリの短絡に関する質問です。また、一般的な人々がチェック・ザ・エグゼキュートの問題をどのように解決するかを聞くのにも興味があります。

(Web)ユーザーがレコードを所有しているか、レコードが変更されているかなど、条件付きで結果を返すだけのクエリをたくさん書いています。 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;

結果は(所有者です):

is_owner | is_newer | json
t          t          {...}

そして否定的な結果(所有者ではない):

is_owner | is_newer | json
f          t          NULL

そのため、アプリケーションはどのエラーを返すかを認識していますが、条件が満たされない場合にビューを構築または解析する必要はありません。

ただし、EXPLAIN ANALYZEは中央のJOINに結果がない場合でも、最後のLEFT LATERAL JOINでビュークエリを実行しますを示し、短絡を防止して、 (高価)view_dataset SELECTが実行されていません。 jsonnullに設定すると、クエリは最初のSELECT以外のすべてをスキップします。しかし、それが最後のクエリの値に設定されている場合、常にすべてのSELECTを実行するので、クエリプランナーは、最上位のSELECTクエリのそのjsonフィールドの結果を取得する必要があると考え、 t JOINを短絡します。

高価なビュークエリをPostgresqlに強制的にドロップさせることができるかどうか疑問に思います。

また、JOINクエリをスキップするように見えるCTEも試しました。

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

...しかし、このクエリとバリエーションは少なくとも2倍遅くなります。

したがって、私の質問は、条件に基づいてJOINまたはサブクエリを短絡してパフォーマンスを最大化する方法です。また、レコードの所有権の確認など、最初に実行してから実行するチェックのパターンを実装する方法が他にあるかどうかを知りたいと思っています。

7
wvh

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;

あなたによって? 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;

編集。テーブル値関数

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;

次に、クエリでその関数を次のように使用します。

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

すべて未テスト。

1
Lennart