PostgreSQLは、where句(述語)を適用するのに、3つの方法を使い分けます。
- アクセス述語 (「Index Cond」)
アクセス述語は、リーフノードの走査の開始と終了の条件を表しています。
- インデックスフィルタ述語 (「Index Cond」)
インデックスフィルタ述語は、リーフノード走査の時にのみ適用されます。スキャンされる範囲の開始と終了の条件を表したり、 範囲を狭めるものとしては使われません。
- テーブルレベルのフィルタ述語 (「Filter」)
インデックスの一部でない列に対する述語は、テーブルのレベルで評価されます。その場合データベースは、 まずヒープテーブルから行をロードしなければなりません。
注記
インデックスフィルタ述語は、間違った安心感を持ってしまう原因になりかねません。 インデックスは確かに使われますが、データ量やシステムの負荷が増大すると、 急激にパフォーマンス劣化が起きる可能性があります。
PostgreSQLの実行計画では、アクセス述語とフィルタ述語は分けて表示されません。両方とも「Index Cond」に表示されます。つまり、インデックスフィルタ述語とアクセス述語を区別するために、 実行計画をインデックスの定義と比較する必要があります。
注記
PostgreSQLの実行計画が提供する情報は、インデックスフィルタ述語を見つけるのには十分とは言えません。
Index Scan
とある場合でも、
「Filter」と表示されているのは常にテーブルレベルの述語になります。
協力してください
この記事が気に入ったら、私の書いた本「SQLパフォーマンス詳解」や私によるトレーニングもきっと気にいるはず。
パフォーマンスとスケーラビリティの
章から抜粋した次の例を考えてみましょう(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)
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)
インデックスではID1
がID2
よりも前にあるため、
ID2
に対する条件はリーフノードの走査の範囲を狭めないという点に注意しましょう。つまりIndex
Scan
では、SECTION=1::numeric
の条件に対する全範囲をスキャンした後、
SECTION
に対する句を満たすようにID2=($1)::numeric
の
各行をフィルタしていきます。