ぱと隊長日誌

ブログ運用もエンジニアとしての生き方も模索中

PostgreSQL の実行計画に現れる One-Time Filter の読み解き方

テーマ

PostgreSQLの実行計画に現れる One-Time Filter とは、クエリ―実行時に1回だけ評価すればよいフィルターのことです。

例えば、次のようなクエリーで現れます。

SELECT * FROM pg_type WHERE CURRENT_DATE = '2018-12-01'::date;

このWHERE句はクエリー実行時に1度だけ評価すれば決まります。このような場合、実行計画に One-Time Filter として出力されます。

この One-Time Filter について、以下の観点で調べてみました。

  • Filter と One-Time Filter の違い
  • One-Time Filter の出現パターン

検証は PostgreSQL 11.0 で行いました。

なお、PostgreSQLのバージョンによって実行計画の出力結果に若干の違いがあるようですので、ご注意ください。

Filter と One-Time Filter の違い

実際の実行計画で確認します。

(1) Filter のみ
=# EXPLAIN ANALYZE SELECT * FROM pg_type WHERE typtype = 'b';
                                               QUERY PLAN
--------------------------------------------------------------------------------------------------------
 Seq Scan on pg_type  (cost=0.00..13.75 rows=142 width=252) (actual time=0.013..0.131 rows=143 loops=1)
   Filter: (typtype = 'b'::"char")
   Rows Removed by Filter: 239

Filter はそのノードで取得した各行に対して評価を行います。(1)だと pg_type の382行のうち、239行がフィルタによって削除され、143行が残ったとわかります。

(2) One-Time Filter のみ
=# EXPLAIN ANALYZE SELECT * FROM pg_type WHERE CURRENT_DATE = '2018-12-01'::date;
                                       QUERY PLAN
-----------------------------------------------------------------------------------------
 Result  (cost=0.01..12.80 rows=380 width=252) (actual time=0.004..0.004 rows=0 loops=1)
   One-Time Filter: (CURRENT_DATE = '2018-12-01'::date)
   ->  Seq Scan on pg_type  (cost=0.01..12.80 rows=380 width=252) (never executed)

One-Time Filter は1回限りの評価です。(2)では One-Time Filter が先に評価され、WHERE句を満たさないことが分かったため、pg_type のスキャンは実行されませんでした。

(3) Filter と One-Time Filter
=# EXPLAIN ANALYZE SELECT * FROM pg_type WHERE typtype = 'b' AND CURRENT_DATE = '2018-12-01'::date;
                                       QUERY PLAN
-----------------------------------------------------------------------------------------
 Result  (cost=0.01..13.75 rows=142 width=252) (actual time=0.004..0.004 rows=0 loops=1)
   One-Time Filter: (CURRENT_DATE = '2018-12-01'::date)
   ->  Seq Scan on pg_type  (cost=0.01..13.75 rows=142 width=252) (never executed)
         Filter: (typtype = 'b'::"char")

WHERE句に Filter と One-Time Filter となる条件を組み合わせた場合が(3)になります。Filter は Seq Scan ノードで、One-Time Filter は Result ノードで評価されています。この場合も(2)と同様に One-Time Filter が先に評価され、Seq Scan は実行されませんでした。

One-Time Filter の出現パターン

調査の過程で見つかった主なパターンをご紹介します。他にもあればぜひご連絡ください。

PostgreSQLプランナはプランニングの段階で評価結果の true / false が明確な場合、それに応じたプランを生成しているようです。

(4)
=# EXPLAIN SELECT * FROM pg_type WHERE true;
                         QUERY PLAN
------------------------------------------------------------
 Seq Scan on pg_type  (cost=0.00..12.80 rows=380 width=252)
(5)
=# EXPLAIN SELECT * FROM pg_type WHERE 'a' IN ('a', 'b', 'c');
                         QUERY PLAN
------------------------------------------------------------
 Seq Scan on pg_type  (cost=0.00..12.80 rows=380 width=252)

(4)のようにWHERE句が true であることが明確な場合、One-Time Filter が出力されません。これは(5)のようにプランニングの段階で評価可能な場合も含まれています。

(6)
=# EXPLAIN SELECT * FROM pg_type WHERE false;
                 QUERY PLAN
--------------------------------------------
 Result  (cost=0.00..0.00 rows=0 width=235)
   One-Time Filter: false
(7)
=# EXPLAIN SELECT * FROM pg_type WHERE 'd' IN ('a', 'b', 'c');
                 QUERY PLAN
--------------------------------------------
 Result  (cost=0.00..0.00 rows=0 width=235)
   One-Time Filter: false

(6)のようにWHERE句が false であることが明確な場合、One-Time Filter は出力されますが、スキャン(例:Seq Scan)は出力されません。プランニングの段階で評価可能な(7)でも同様です。

(8)
=# EXPLAIN SELECT * FROM pg_type WHERE CURRENT_DATE = '2018-12-01'::date;
                            QUERY PLAN
------------------------------------------------------------------
 Result  (cost=0.01..12.80 rows=380 width=252)
   One-Time Filter: (CURRENT_DATE = '2018-12-01'::date)
   ->  Seq Scan on pg_type  (cost=0.01..12.80 rows=380 width=252)

(8)の場合はプランニングの段階でWHERE句を評価できないため、そのまま One-Time Filter に出力されています。実行する際に One-Time Filter を評価し、条件を満たさなければ下位ノードの Seq Scan は実行されません。

(9)
=# EXPLAIN SELECT * FROM pg_type WHERE typtype = 'b' AND EXISTS (SELECT 1 WHERE 1+1=3);
                            QUERY PLAN
------------------------------------------------------------------
 Result  (cost=0.01..13.76 rows=142 width=252)
   One-Time Filter: $0
   InitPlan 1 (returns $0)
     ->  Result  (cost=0.00..0.01 rows=1 width=0)
           One-Time Filter: false
   ->  Seq Scan on pg_type  (cost=0.01..13.76 rows=142 width=252)
         Filter: (typtype = 'b'::"char")

(9)の場合、WHERE句が false となることは明白ですが、プランニングの段階ではそこまで評価せず、そのままプランに出力されています。

EXISTS句が "One-Time Filter: $0" として出力されています。これは EXISTS句内のクエリがその他のクエリの結果に依存しておらず、単独で実行可能かつ1回限りの評価でよいためと思われます。

EXISTS句内の "SELECT 1 WHERE 1+1=3" は "InitPlan 1 (returns $0)" として出力されています。このSELECT結果が $0 に代入され、"One-Time Filter: $0" で評価されます。

このケースで Seq Scan は実行されません。

参考資料

PostgreSQL プラン・ツリーの概要
Resultノードの説明の中で One-Time Filter について少し触れられています。