web-dev-qa-db-ja.com

単純結合で使用されていない主キーのインデックス

次のテーブルとインデックスの定義があります。

CREATE TABLE munkalap (
    munkalap_id serial PRIMARY KEY,
    ...
);

CREATE TABLE munkalap_lepes (
    munkalap_lepes_id serial PRIMARY KEY,
    munkalap_id integer REFERENCES munkalap (munkalap_id),
    ...
);

CREATE INDEX idx_munkalap_lepes_munkalap_id ON munkalap_lepes (munkalap_id);

次のクエリでmunkalap_idのインデックスが使用されないのはなぜですか?

EXPLAIN ANALYZE SELECT ml.* FROM munkalap m JOIN munkalap_lepes ml USING (munkalap_id);

QUERY PLAN
Hash Join  (cost=119.17..2050.88 rows=38046 width=214) (actual time=0.824..18.011 rows=38046 loops=1)
  Hash Cond: (ml.munkalap_id = m.munkalap_id)
  ->  Seq Scan on munkalap_lepes ml  (cost=0.00..1313.46 rows=38046 width=214) (actual time=0.005..4.574 rows=38046 loops=1)
  ->  Hash  (cost=78.52..78.52 rows=3252 width=4) (actual time=0.810..0.810 rows=3253 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 115kB
        ->  Seq Scan on munkalap m  (cost=0.00..78.52 rows=3252 width=4) (actual time=0.003..0.398 rows=3253 loops=1)
Total runtime: 19.786 ms

フィルターを追加しても同じです:

EXPLAIN ANALYZE SELECT ml.* FROM munkalap m JOIN munkalap_lepes ml USING (munkalap_id) WHERE NOT lezarva;

QUERY PLAN
Hash Join  (cost=79.60..1545.79 rows=1006 width=214) (actual time=0.616..10.824 rows=964 loops=1)
  Hash Cond: (ml.munkalap_id = m.munkalap_id)
  ->  Seq Scan on munkalap_lepes ml  (cost=0.00..1313.46 rows=38046 width=214) (actual time=0.007..5.061 rows=38046 loops=1)
  ->  Hash  (cost=78.52..78.52 rows=86 width=4) (actual time=0.587..0.587 rows=87 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 4kB
        ->  Seq Scan on munkalap m  (cost=0.00..78.52 rows=86 width=4) (actual time=0.014..0.560 rows=87 loops=1)
              Filter: (NOT lezarva)
Total runtime: 10.911 ms
17
dezso

多くの人々は、「シーケンシャルスキャンは悪い」というガイダンスを聞いて、計画からそれらを排除しようとしていますが、それほど単純ではありません。クエリがテーブルのすべての行をカバーする場合は、順次スキャンがそれらの行を取得する最速の方法です。両方のテーブルのすべての行が必要だったため、元の結合クエリでseqスキャンが使用されたのはこのためです。

クエリを計画するとき、Postgresのプランナはさまざまな可能なスキームの下でさまざまな操作(計算、順次、ランダムIO)のコストを見積もり、見積もりが最も低いコストのプランを選びます。回転ストレージ(ディスク)からIOを実行する場合、ランダムIOは通常、シーケンシャルIOよりも実質的に遅くなります-- random_page_costおよびseq_page_costのデフォルトのpg構成 コストの4:1の差を推定します。

これらの考慮事項は、インデックスを使用する結合方法またはフィルター方法と、テーブルを順次スキャンする方法を検討する場合に役立ちます。インデックスを使用する場合、プランはインデックスを介して行をすばやく見つけ、行データを解決するためにランダムブロック読み取りを考慮する必要があります。フィルタリング述語WHERE NOT lezarvaを追加した2番目のクエリの場合、これがEXPLAIN ANALYZEの結果で計画の見積もりにどのように影響したかを確認できます。プランナーは、結合から得られた1006行を推定します(これは、実際の結果セット964にかなり一致しています)。大きなテーブルmunkalap_lepesに約38K行が含まれている場合、プランナーは、結合がテーブル内の行の約1006/38046または1/38にアクセスする必要があることを確認します。また、行の平均幅が214バイトでブロックが8Kであることもわかっているため、ブロックあたり約38行になります。

これらの統計を使用して、プランナは結合がテーブルのデータブロックのすべてまたはほとんどを読み取る必要がある可能性が高いと見なします。インデックスルックアップも無料ではなく、フィルター条件を評価するブロックをスキャンする計算はIOに比べて非常に安価であるため、プランナーはテーブルを順次スキャンし、インデックスオーバーヘッドとランダムスキャンを回避してseqスキャンを計算することを選択しましたより速くなります。

現実の世界では、多くの場合、OSページキャッシュを介してメモリ内のデータを利用できるため、すべてのブロック読み取りにIOが必要なわけではありません。特定のクエリに対してキャッシュがどの程度効果的であるかを予測することは非常に困難ですが、Pgプランナーはいくつかの単純なヒューリスティックを使用します。構成値 effective_cache_size は、実際に発生する可能性についてプランナの推定値を通知しますIOコスト。値が大きいほど、ランダムなコストを低く見積もるIOであるため、順次スキャンよりもインデックス駆動型の方法にバイアスがかかる場合があります。

25
dbenhur

両方のテーブルからすべての行を取得しているため、インデックススキャンを使用しても実際にはメリットはありません。インデックススキャンは、テーブルから数行のみを選択している場合にのみ意味があります(通常は10%〜15%未満)。