by Hayato Matsuura.

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


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)

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

ヒント

著者について

Markus Winandの写真

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

彼の本

カバー『SQLパフォーマンス詳解』

核心をわかりやすく 解説。

Markusから購入します
(送料無料+PDF)

Amazonで購入
(印刷版のみ)

“Use The Index, Luke!” by Markus Winand is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License.
法律上の通知 | 接触 | 無保証 | 商標 | Privacy | CC-BY-NC-ND 3.0 license