クエリの最適化。 PostgreSQLのEXPLAINの基本(パート3)



私は、ギヨーム・レラージによる「理解の説明」の著者による改訂版の発行を続けています。

繰り返しになりますが、簡潔にするために情報の一部が省略されていることに注意してください。そのため、元の情報に慣れることを強くお勧めします。

前のパーツ:


パート1

パート2



ORDER BY



DROP INDEX foo_c1_idx; EXPLAIN (ANALYZE) SELECT * FROM foo ORDER BY c1;
      
      





クエリプラン

-並べ替え(コスト= 117993.01..120493.04行= 1000010幅= 37)(実際の時間= 571.591..651.524行= 1000010ループ= 1)

ソートキー:c1

ソート方法:外部マージディスク:45952kB

-> fooのSeqスキャン(コスト= 0.00..18334.10行= 1000010幅= 37)(実際の時間= 0.007..62.041行= 1000010ループ= 1)

合計ランタイム:690.984ミリ秒

(5行)



最初に、 Seq Scan



テーブルfoo



生成されます。 次にSort



ます。 EXPLAINコマンドの出力では、記号->



はアクションの階層( node )を示します 。 アクションが早く実行されると、よりインデントされて表示されます。

Sort Key



-ソート条件。

Sort Method: external merge Disk



-ソート時に45952kB



45952kB



のディスクボリューム上の一時ファイル45952kB



ます。



このトピックを理解している人に、 external merge



external sort



違いを説明してもらいexternal sort







BUFFERSオプションで確認します。

 EXPLAIN (ANALYZE,BUFFERS) SELECT * FROM foo ORDER BY c1;
      
      





クエリプラン

-並べ替え(コスト= 117993.01..120493.04行= 1000010幅= 37)(実際の時間= 568.412..652.308行= 1000010ループ= 1)

ソートキー:c1

ソート方法:外部マージディスク:45952kB

バッファー:共有ヒット= 8334、一時読み取り= 5745書き込み= 5745

-> fooのSeqスキャン(コスト= 0.00..18334.10行= 1000010幅= 37)(実際の時間= 0.010..68.203行= 1000010ループ= 1)

バッファー:共有ヒット= 8334

合計ランタイム:698.032ミリ秒

(7行)



実際、 temp read=5745 written=5745



5745-5745ブロックの8Kb = 45960Kbが一時ファイルに書き込まれ、読み取られました。 8334ブロックの操作がキャッシュで実行されました。



ファイルシステムの操作は、メモリ内の操作よりも遅くなります。

work_mem



使用するメモリ量を増やしてみましょう。

 SET work_mem TO '200MB'; EXPLAIN (ANALYZE) SELECT * FROM foo ORDER BY c1;
      
      





クエリプラン

-並べ替え(コスト= 117993.01..120493.04行= 1000010幅= 37)(実際の時間= 265.301..296.777行= 1000010ループ= 1)

ソートキー:c1

ソート方法:クイックソートメモリ:102702kB

-> fooのSeqスキャン(コスト= 0.00..18334.10行= 1000010幅= 37)(実際の時間= 0.006..57.836行= 1000010ループ= 1)

合計ランタイム:328.746ミリ秒

(5行)



Sort Method: quicksort Memory: 102702kB



全体はRAMで実行されます。



郵便番号:

 CREATE INDEX ON foo(c1); EXPLAIN (ANALYZE) SELECT * FROM foo ORDER BY c1;
      
      





クエリプラン

-fooでfoo_c1_idxを使用したインデックススキャン(コスト= 0.42..34327.57行= 1000010幅= 37)(実際の時間= 0.023..126.076行= 1000010ループ= 1)

合計ランタイム:153.452ミリ秒

(2行)



アクションのうち、 Index Scan



のみが残り、クエリの速度に大きな影響を与えました。



限界



以前に作成したインデックスを削除します。

 DROP INDEX foo_c2_idx1; EXPLAIN (ANALYZE,BUFFERS) SELECT * FROM foo WHERE c2 LIKE 'ab%';
      
      





クエリプラン

-fooのSeqスキャン(コスト= 0.00..20834.12行= 100幅= 37)(実際の時間= 0.033..94.757行= 3824ループ= 1)

フィルター:(c2 ~~ 'ab%' :: text)

フィルターによって削除された行:996186

バッファー:共有ヒット= 8334

合計ランタイム:94.924ミリ秒

(5行)



Seq Scan



and Filter



を使用する予定です。



 EXPLAIN (ANALYZE,BUFFERS) SELECT * FROM foo WHERE c2 LIKE 'ab%' LIMIT 10;
      
      





クエリプラン

-制限(コスト= 0.00..2083.41行= 10幅= 37)(実際の時間= 0.037..0.607行= 10ループ= 1)

バッファー:共有ヒット= 26

-> fooのSeqスキャン(コスト= 0.00..20834.12行= 100幅= 37)(実際の時間= 0.031..0.599行= 10ループ= 1)

フィルター:(c2 ~~ 'ab%' :: text)

フィルターによって削除された行:3053

バッファー:共有ヒット= 26

合計ランタイム:0.628 ms

(7行)



Seq Scan



テーブルの行をSeq Scan



Filter



と条件を比較します。 条件を満たす10個のレコードが満たされるとすぐに、スキャンは終了します。 この場合、結果の10行を取得するには、テーブル全体ではなく3063レコードのみを読み取る必要があり、そのうち3053レコードが拒否されました( Rows Removed by Filter



)。

同じことがIndex Scan



起こります。



参加する



新しいテーブルを作成し、その統計を収集します。

 CREATE TABLE bar (c1 integer, c2 boolean); INSERT INTO bar SELECT i, i%2=1 FROM generate_series(1, 500000) AS i; ANALYZE bar;
      
      







2テーブルクエリ

 EXPLAIN (ANALYZE) SELECT * FROM foo JOIN bar ON foo.c1=bar.c1;
      
      





クエリプラン

-ハッシュ結合(コスト= 13463.00..49297.22行= 500000幅= 42)(実際の時間= 87.441..907.555行= 500010ループ= 1)

ハッシュ条件:(foo.c1 = bar.c1)

-> fooのSeqスキャン(コスト= 0.00..18334.10行= 1000010幅= 37)(実際の時間= 0.008..67.951行= 1000010ループ= 1)

->ハッシュ(コスト= 7213.00..7213.00行= 500000幅= 5)(実際の時間= 87.352..87.352行= 500000ループ= 1)

バケット:65536バッチ:1メモリ使用量:18067kB

-> Seq Scan on Bar(コスト= 0.00..7213.00行= 500000幅= 5)(実際の時間= 0.007..33.233行= 500000ループ= 1)

合計ランタイム:920.967ミリ秒

(7行)



最初に、( bar



)テーブルがスキャンされます( Seq Scan



)。 各行について、ハッシュが計算されます。

次に、 Seq Scan



テーブルfoo



スキャンされ、このテーブルの各行に対してハッシュが計算され、 Hash Cond



条件によってbar



テーブルのハッシュと比較( Hash Join



)されます。 一致が見つかった場合、結果の文字列が表示されます。それ以外の場合、文字列はスキップされます。

メモリの18067kBを使用して、テーブルのbar



ハッシュをホストしました。



インデックスを追加する

 CREATE INDEX ON bar(c1); EXPLAIN (ANALYZE) SELECT * FROM foo JOIN bar ON foo.c1=bar.c1;
      
      





クエリプラン

-結合の結合(コスト= 1.69..39879.71行= 500000幅= 42)(実際の時間= 0.037..263.357行= 500010ループ= 1)

マージ条件:(foo.c1 = bar.c1)

-> fooでfoo_c1_idxを使用したインデックススキャン(コスト= 0.42..34327.57行= 1000010幅= 37)(実際の時間= 0.019..58.920行= 500011ループ= 1)

->バーでbar_c1_idxを使用したインデックススキャン(コスト= 0.42..15212.42行= 500000幅= 5)(実際の時間= 0.008..71.719行= 500010ループ= 1)

合計ランタイム:283.549ミリ秒

(5行)



Hash



使用されなくなりました。 両方のテーブルのインデックスでMerge Join



Index Scan



Merge Join



と、パフォーマンスが大幅に向上します。



左から参加:

 EXPLAIN (ANALYZE) SELECT * FROM foo LEFT JOIN bar ON foo.c1=bar.c1;
      
      





クエリプラン

-ハッシュ左結合(コスト= 13463.00..49297.22行= 1000010幅= 42)(実際の時間= 82.682..926.331行= 1000010ループ= 1)

ハッシュ条件:(foo.c1 = bar.c1)

-> fooのSeqスキャン(コスト= 0.00..18334.10行= 1000010幅= 37)(実際の時間= 0.004..68.763行= 1000010ループ= 1)

->ハッシュ(コスト= 7213.00..7213.00行= 500000幅= 5)(実際の時間= 82.625..82.625行= 500000ループ= 1)

バケット:65536バッチ:1メモリ使用量:18067kB

-> Seq Scan on Bar(コスト= 0.00..7213.00行= 500000幅= 5)(実際の時間= 0.003..31.890行= 500000ループ= 1)

合計ランタイム:950.625ミリ秒

(7行)



Seq Scan





Seq Scanを禁止した場合の結果を見てみましょう。

 SET enable_seqscan TO off; EXPLAIN (ANALYZE) SELECT * FROM foo LEFT JOIN bar ON foo.c1=bar.c1;
      
      





クエリプラン

-左結合のマージ(コスト= 0.85..58290.02行= 1000010幅= 42)(実際の時間= 0.024..353.819行= 1000010ループ= 1)

マージ条件:(foo.c1 = bar.c1)

-> fooでfoo_c1_idxを使用したインデックススキャン(コスト= 0.42..34327.57行= 1000010幅= 37)(実際の時間= 0.011..112.095行= 1000010ループ= 1)

->バーのbar_c1_idxを使用したインデックススキャン(コスト= 0.42..15212.42行= 500000幅= 5)(実際の時間= 0.008..63.125行= 500010ループ= 1)

合計ランタイム:378.603ミリ秒

(5行)



プランナーによると、インデックスを使用する方がハッシュを使用するよりも高価です。 これは、十分な量の割り当てられたメモリで可能です。 work_mem



を増やしたことを覚えていwork_mem



か?

ただし、メモリが不足している場合、スケジューラの動作は異なります。

 SET work_mem TO '15MB'; SET enable_seqscan TO ON; EXPLAIN (ANALYZE) SELECT * FROM foo LEFT JOIN bar ON foo.c1=bar.c1;
      
      





クエリプラン

-左結合のマージ(コスト= 0.85..58290.02行= 1000010幅= 42)(実際の時間= 0.014..376.395行= 1000010ループ= 1)

マージ条件:(foo.c1 = bar.c1)

-> fooでfoo_c1_idx1を使用したインデックススキャン(コスト= 0.42..34327.57行= 1000010幅= 37)(実際の時間= 0.005..124.698行= 1000010ループ= 1)

->バーのbar_c1_idxを使用したインデックススキャン(コスト= 0.42..15212.42行= 500000幅= 5)(実際の時間= 0.006..66.813行= 500010ループ= 1)

合計ランタイム:401.990ミリ秒

(5行)



禁止されたIndex Scan



は、EXPLAIN出力はどのようになりますか?

 SET work_mem TO '15MB'; SET enable_indexscan TO off; EXPLAIN (ANALYZE) SELECT * FROM foo LEFT JOIN bar ON foo.c1=bar.c1;
      
      





クエリプラン

-ハッシュ左結合(コスト= 15417.00..63831.18行= 1000010幅= 42)(実際の時間= 93.440..712.056行= 1000010ループ= 1)

ハッシュ条件:(foo.c1 = bar.c1)

-> fooのSeqスキャン(コスト= 0.00..18334.10行= 1000010幅= 37)(実際の時間= 0.008..65.901行= 1000010ループ= 1)

->ハッシュ(コスト= 7213.00..7213.00行= 500000幅= 5)(実際の時間= 93.308..93.308行= 500000ループ= 1)

バケット:65536バッチ:2メモリ使用量:9045kB

-> Seq Scan on Bar(コスト= 0.00..7213.00行= 500000幅= 5)(実際の時間= 0.007..33.718行= 500000ループ= 1)

合計ランタイム:736.726ミリ秒

(7行)



cost



明らかに増加しました。 Batches: 2



理由Batches: 2



。 ハッシュ全体がメモリに収まらず、それぞれ9045kBの2つのパケットに分割する必要がありました。



ここでも、教祖の助けを求めます。 LEFT JOIN



と十分なwork_mem



Merge Left Join



使用がHash Left Join



よりも高価な理由を説明してください。




今日はここで止まります。



UPD。

PostgreSQLインデックスに関する多くの有用なことがOleg BartunovとAlexander Korotkov によって語られました



ここでは、PostgreSQLのPostgreSQL インデックスパート2パート3の最新記事へのリンクを紹介します 。 多くのことが明らかにされています。



All Articles