アクセス述語とフィルタ述語の見分け方


PostgreSQLは、where句(述語)を適用するのに、 3つの方法を使い分けます。

アクセス述語 (「Index Cond」)

アクセス述語は、リーフノードの走査の開始と終了の条件を 表しています。

インデックスフィルタ述語 (「Index Cond」)

インデックスフィルタ述語は、リーフノード走査の時にのみ適用されます。 スキャンされる範囲の開始と終了の条件を表したり、 範囲を狭めるものとしては使われません。

テーブルレベルのフィルタ述語 (「Filter」)

インデックスの一部でない列に対する述語は、 テーブルのレベルで評価されます。その場合データベースは、 まずヒープテーブルから行をロードしなければなりません。

注記

インデックスフィルタ述語は、間違った安心感を持ってしまう原因になりかねません。 インデックスは確かに使われますが、データ量やシステムの負荷が増大すると、 急激にパフォーマンス劣化が起きる可能性があります。

PostgreSQLの実行計画では、アクセス述語と フィルタ述語は分けて表示されません。両方とも「Index Cond」に表示されます。 つまり、インデックスフィルタ述語とアクセス述語を区別するために、 実行計画をインデックスの定義と比較する必要があります。

注記

PostgreSQLの実行計画が提供する情報は、 インデックスフィルタ述語を見つけるのには十分とは言えません。

Index Scanとある場合でも、 「Filter」と表示されているのは常にテーブルレベルの述語になります。

このウェブサイトにぴったりのカップは僕たちのショップにあります。
#見た目もいい感じだし、ここでの僕の仕事を支えてくれています

パフォーマンスとスケーラビリティの 章から抜粋した次の例を考えてみましょう(createおよびinsert スクリプト)。

CREATE TABLE scale_data (
   section NUMERIC NOT NULL,
   id1     NUMERIC NOT NULL,
   id2     NUMERIC NOT NULL
);
CREATE INDEX scale_data_key ON scale_data(section, id1);

次のselectは、インデックスに含まれていない ID2列でフィルタしています。

PREPARE stmt(int) AS SELECT count(*) 
                       FROM scale_data
                      WHERE section = 1
                        AND id2 = $1;
EXPLAIN EXECUTE stmt(1);
                      QUERY PLAN
-----------------------------------------------------
Aggregate  (cost=529346.31..529346.32 rows=1 width=0)
  Output: count(*)
  -> Index Scan using scale_data_key on scale_data
     (cost=0.00..529338.83 rows=2989 width=0)
     Index Cond: (scale_data.section = 1::numeric)
     Filter: (scale_data.id2 = ($1)::numeric)

ID2の述語は、Index Scan処理の後に 「Filter」として現われています。これは、 PostgreSQLはIndex Scanの一部としてテーブルアクセスを 行うためです。言い換えると、PostgreSQLのIndex Scan処理には Oracleで言うTABLE ACCESS BY INDEX ROWIDが 隠されているのです。そのため、 インデックスに含まれていない列に対するIndex Scanも 可能と言う事になります。

重要

PostgreSQLのFilter述語は、 Index Scanとある場合でも、テーブルレベルの フィルタ述語です。

パフォーマンスとスケーラビリティ」の章で やったのと同じようにインデックスを追加しても、 アクセス述語かフィルタ述語かに関係なく、全ての列が「Index Cond」に 表示されます。

CREATE INDEX scale_slow ON scale_data (section, id1, id2)

新しいインデックスを作成した後の実行計画では、 フィルタ述語は表示されなくなります。

                      QUERY PLAN
------------------------------------------------------
Aggregate  (cost=14215.98..14215.99 rows=1 width=0)
  Output: count(*)
  -> Index Scan using scale_slow on scale_data 
     (cost=0.00..14208.51 rows=2989 width=0)
     Index Cond: (section = 1::numeric AND id2 = ($1)::numeric)

インデックスではID1ID2よりも前にあるため、 ID2に対する条件はリーフノードの走査の範囲を狭めないという点に 注意しましょう。つまりIndex Scanでは、 SECTION=1::numericの条件に対する全範囲をスキャンした後、 SECTIONに対する句を満たすようにID2=($1)::numericの 各行をフィルタしていきます。

ヒント

Photo of Markus Winand
Markus Winand氏は、開発者がSQLパフォーマンスを改善するお手伝いをしています。 彼は、SQL Performance Explainedの 著者でもあり、出張トレーニングhttp://winand.at/での リモート講義も 行っています。