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


16

次の表とインデックスの定義があります。

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

回答:


21

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

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

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

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

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


おかげで、これは今まで読んだ中で最高の(そして最も簡潔な)説明です。いくつかのキーポイントを明確にしました。
-dezso

1
素晴らしい説明。ただし、行/データページの計算は少しずれています。ページヘッダー(24バイト)+行ごとのアイテムポインターごとに4バイト+行ヘッダーHeapTupleHeader(行ごとに23バイト)+ NULLビットマスク+ MAXALIGNに基づくアライメントを考慮する必要があります。最後に、列のデータ型とそのシーケンスに応じたデータのアライメントによる不明な量のパディング。この場合、全体で8 kbページには33行以下です。(TOASTを考慮していません。)
アーウィンブランドステッター

1
@ErwinBrandstetterより厳密な行サイズの計算を入力していただきありがとうございます。Explainによる行幅の推定出力には、ヘッダーやNULLビットマスクなどの行ごとの考慮事項が含まれるが、ページレベルのオーバーヘッドは含まれないと常に考えていました。
-dbenhur

1
@dbenhur:EXPLAIN ANALYZE SELECT foo from bar確認のために、基本的なダミーテーブルを使用してクイックを実行できます。また、実際のディスク上のスペースはデータのアライメントに依存します。これは、一部の行のみが取得される場合に考慮するのが難しいでしょう。の行幅はEXPLAIN、取得された列セットの基本的なスペース要件を表します。
アーウィンブランドステッター

5

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


うん、あなたは正しいです:)私はより具体的なケースで状況を明確にしようとしました、最後のクエリを参照してください。
-dezso

@dezso:同じこと。インデックスがオンで(lezarva, munkalap_id)、十分に選択的である場合は、それを使用できます。NOTその劣勢になります。
ypercubeᵀᴹ

私はあなたの提案に基づいて部分的なインデックスを追加し、それが使用されたので、問題の半分は解決されました。しかし、私は、元3252.に比べて唯一の87の値に対して、参加したいことを考えると役に立たないという外部キーにインデックスを期待していない
dezso

1
@dezso行の平均幅は214バイトなので、8Kデータブロックあたり40行弱になります。インデックスの選択性も約1/40(1006/38046)です。したがって、Pgは、インデックスを使用する場合、すべてのブロックを順番に読み取ることは、ほぼ同じ数のブロックをランダムに読み取る可能性よりも安価であると考えています。これらの推定トラッドオフは、effective_cache_sizeおよびrandom_page_cost構成値の影響を受ける可能性があります。
-dbenhur

@dbenhur:あなたのコメントを適切な答えにできますか?
dezso
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.