1
実行計画
概要
• 実行計画の取得方法・読み方について学習し、パフォーマンス
の問題の調査方法に役立てます。
2
目次
• SQL実行までの処理の流れ
• 実行計画
• テーブルフルスキャン
• インデックススキャン
• インデックスオンリースキャン
• 結合について
• 結合の実行計画
• 結合のアルゴリズム(Nested Loops, Hash, Merge Join)
• 演習問題
• 統計情報
• 参考資料
• Oracleの実行計画
3
前提条件
• このテキストの内容はPostgreSQL(バージョン9.6.9)の環境で検証し
たものとなっています。
• テキストと同じ環境で検証する場合はPostgreSQLをインストールし、
事前にデータベース(axizdb)とユーザー(axizuser)を作成してくだ
さい。
4
SQL実行までの処理の流れ
5
オプティマイザ
SQL実行までの処理の流れ
• 処理の流れ
6
①パーサ
②プラン生成 ③コスト評価
⑤プラン評価
④カタログ
マネージャ
クエリ
パース済みのクエリ
実行計画
SQL実行までの処理の流れ
• パーサ
• パーサは、受け取ったSQL文のパース(構文解析)を行います。
• 受け取ったSQLが文法的に間違っていないかをチェックします。一般
的なプログラミング言語のコンパイル時に行われるものと同様です。
• また、受け取ったSQL文を一度バラバラに分解して、それをDBMSが処
理しやすい形式に変換します。
7
SQL実行までの処理の流れ
• オプティマイザ
• オプティマイズとは、「最適化」という意味です。
• オプティマイザは、発行されたSQL文の最適なデータアクセスの方法
を決定する部分になります。
• 選択可能な実行計画を作成し、その中から最もコストが低い計画を絞
り込みます。
8
SQL実行までの処理の流れ
• カタログマネージャ
• カタログマネージャは、オプティマイザが実行計画を立てる際に必要
となる重要な情報を提供します。
• DBMSは、テーブルの「統計情報」やインデックスに関する情報を持っ
ています。それらを管理する部分です。
9
SQL実行までの処理の流れ
• プラン評価
• オプティマイザが複数の実行計画を立てた後、それを受け取って、最
適な実行計画を選択するのがプラン評価です。
• 実行計画が一つに絞り込まれた後、DBMSは実行計画を実行可能なコー
ドに変換してデータへアクセスします。
10
実行計画
11
実行計画
• 実行計画とは
・実行計画とは、データアクセス方法のことです。
・DBMSは、SQLが発行されると、まずはそのSQL文の構文があっ
ているかをチェックします。(パーサ)
・その後、オプティマイザと呼ばれる最適化のためのソフト
ウェアが実行計画を生成します。
12
実行計画
• 実行計画の確認方法
• コマンドで実行する場合は、DBMSによって異なります。
• このテキストではPostgreSQLで検証しながら学習を進めます。
13
DBMS コマンド
Oracle SET AUTOTRACE TRACEONLY
SQL Server SET SHOWPLAN_TEXT ON
DB2 EXPLAIN ALL WITH SNAPSHOT FOR SQL文
PostgreSQL EXPLAIN SQL文
MySQL EXPLAIN EXTENDED SQL文
実行計画
• 実行計画で使用するテーブル定義
• 今回の検証で使用するのは以下の2つのテーブルです。
• それぞれ10万件ずつのレコードが存在していることを前提とします。
14
tableA インデックス
物理名 型 primary key 1 a_id
a_id integer 〇
name varchar(50)
value real
tableB インデックス
物理名 型 primary key 1 b_id
b_id integer 〇 2 a_id
a_id integer
value real
実行計画
• 実行計画の例
• 以下はPostgreSQLの実行計画の例です。
15
実行計画
• ここでは、以下の4つの実行計画を確認していきます。
• テーブルフルスキャン
• インデックススキャン
• インデックスオンリースキャン
• テーブルの結合
• Nested loops
• Hash
• Sort Merge
16
テーブルフルスキャン
• psqlを起動し、実際に実行計画を見てみます。
• 以下のコマンドを実行してみましょう。
17
axizdb=> EXPLAIN SELECT * FROM tableA;
テーブルフルスキャン
• 出力された実行計画を確認してみます。
• Seq Scanはテーブルへのアクセス方法を示しており、テーブル
フルスキャンが実行されています。
• 括弧の中は、SQLの実行にかかるコスト、出力される行数、出
力されるバイト数が表示されます。
18
テーブルフルスキャン
• テーブルフルスキャン
• テーブルフルスキャンは、対象のテーブルに対する全てのレコードを
読み込みます。
• PostgreSQLの場合は「Seq Scan」
• レコード数の多いテーブルに対してテーブルフルスキャンが行われて
いた場合パフォーマンスを低下させる可能性が高いです。
19
インデックススキャン
• 次はWHERE句を使用して条件の絞り込みをしたSQLの場合を見
てみます。
• 以下のコマンドを実行してみましょう。
20
axizdb=> EXPLAIN SELECT * FROM tableA WHERE a_id = 1;
インデックススキャン
• 出力された実行計画を確認してみます。
• テーブルへのアクセス方法がIndex Scan になり、インデックス
が使用されているのが分かります。
• Index Scan using <インデックス名> on <テーブル名>
21
インデックススキャン
• インデックススキャン
• インデックススキャンでは、一旦インデックスを使用して対象のレ
コードを絞り込み、対象のレコードのみを取得します。
• テーブルフルスキャンよりもディスクへのアクセス回数を減らすこと
ができるため、高いパフォーマンスが期待できます。
• テーブルに対してインデックスが複数存在する場合は、期待したイン
デックスが使用されているかどうかも確認しましょう。
22
インデックスオンリースキャン
• 次は先ほどのSQLからSELECT句で取得するカラムを一つにしま
す。
• 以下のコマンドを実行してみましょう。
23
axizdb=> EXPLAIN SELECT a_id FROM tableA WHERE a_id = 1;
インデックスオンリースキャン
• 出力された実行計画を確認してみます。
• テーブルへのアクセス方法がIndex Only Scan になっています。
• 先ほどと違うのは、SELECT句に指定したカラムをa_idのみにし
ていることです。これはインデックスに含まれているカラムな
ので、インデックスのみのアクセスでデータを取得することが
できます。
24
インデックスオンリースキャン
• インデックスオンリースキャン
• SELECT句に指定したカラムがインデックスに含まれる項目のみの場合
はインデックスオンリースキャンによるアクセスになることがありま
す。
• レコードにはアクセスせず、インデックスのみのアクセスでデータを
取得できるため、高いパフォーマンスが期待できます。
• SELECT句で指定するカラムが限られるので、使用される頻度は少ない
です。
25
実行計画まとめ
• 実行計画まとめ
• テーブルへのアクセス方法は、テーブルフルスキャン、インデックス
スキャン、インデックスオンリースキャンなどがある。
• パフォーマンスが悪いときは、実行計画を確認し、テーブルへのアク
セス方法を確認する。
• テーブルフルスキャンによってパフォーマンスが低下している場合は、
インデックスが使用されるようなSQL文、またはインデックス設計を
検討する。
26
実行計画まとめ
• 実行計画まとめ
• 実行計画の確認方法や表示のされ方は、DBMS製品によって異なります。
詳しくは各DBMSのマニュアル等を参照してください。
• どのDBMSであっても、実行計画で重要なのは「インデックスの使用の
有無」と「どのインデックスが使用されているか」です。
27
結合について
28
結合の実行計画
• 結合の実行計画
• SQLでパフォーマンスが悪いとき、結合の処理が影響している可能性
が高いです。
• 結合では主に3つのアルゴリズムが使用されます。
29
結合のアルゴリズム
• 結合のアルゴリズム
結合のアルゴリズムは、DBMSによってサポートされているものが異な
りますが、よく使用されているのは次の3つです。
• Nested Loops
• Hash
• Sort Marge
どれが使用されるかはオプティマイザが判断します。
一つ一つ確認していきましょう。
30
Nested Loops
• まずは以下のSQL文の実行計画を確認します。
• 実行結果は2レコードになるSQL文になっています。
31
axizdb=> EXPLAIN SELECT * FROM tableA a
INNER JOIN tableB b
ON a.a_id = b.a_id
WHERE b.value = 1
AND b.a_id BETWEEN 10 AND 200;
Nested Loops
• 実行計画を確認してみます。
• Nested Loop表示されているのが分かります。
• Nested Loopsのアルゴリズムで結合が行われています。
32
Nested Loops
• Nested Loops
• Nested Loopsが結合の最もメジャーなアルゴリズムです。
• このようなSQL文を実行するとき、取得するレコードが少ない方の
テーブルを「駆動表」といいます。
• 「駆動表」でない方のテーブルを「内部表」といいます。
33
SELECT * FROM table1
INNER JOIN table2
ON table1.column = table2.column
Nested Loops
• Nested Loopsのアルゴリズム
• 駆動表を1行ずつループしながらスキャンする。
• 駆動表の1行に対して、内部表を1行ずるスキャンして、結合条件に合
致する場合はそれを取得する。
• その処理を駆動表のすべてのループに対して実施。
結合の処理を速くなる条件は
• 「駆動表を小さくする」
• 「内部表の結合キーにインデックスが存在する」
の2つがポイントになります。
34
Hash
• 先ほどのSQLから少し条件を変更してみます。
• 今回は実行結果は1000レコード出力されます。
35
axizdb=> EXPLAIN SELECT * FROM tableA a
INNER JOIN tableB b
ON a.a_id = b.a_id
WHERE b.value = 1;
Hash
• Hash Joinと表示されているのが分かります。
• Hashのアルゴリズムで結合が行われています。
36
Hash
• Hashのアルゴリズム
• 小さいテーブルに対してスキャンし、結合キーに対してハッシュ関数
を適用することでハッシュ値に変換する。それをハッシュテーブルと
呼ぶ。
• そしてもう一方のテーブルをスキャンし、そのハッシュ値がハッシュ
テーブルに存在するかどうかを調べる。
• 合致したら結合を行う。
• Hashの特徴
• ハッシュテーブルを作成する必要があるため、Nested Loopsよりも多く
のメモリを消費する。
• 等値結合(=での結合)でのみ使用可能。
• Nested Loopsで適切な駆動表が存在しない場合や、結合キーにインデッ
クスが存在しない場合などでは、Hashが有効なケースとなります。
37
Sort Merge
• 次はWHERE句を無くしてみます。
• 100000レコード出力されるSQLになります。
38
axizdb=> EXPLAIN SELECT * FROM tableA a
INNER JOIN tableB b
ON a.a_id = b.a_id;
Sort Merge
• ここではMerge Joinと表示されているのが分かります。
• Sort Mergeによる結合が行われています。
39
Sort Merge
• Sort Mergeのアルゴリズム
• テーブルをそれぞれ結合キーでソートする。
• 片方のテーブルをスキャンしながら一致する結合キーが存在したら、
結合する。
• Sort Mergeの特徴
• 対象のテーブルを両方ソートするため、多くのメモリを使用する。
• Hashとは違って不等号を使った結合も可能。
40
結合のアルゴリズム
• ケース別の最適なアルゴリズム
• 小 – 小
• どんなアルゴリズムでも性能差はほとんどない。
• 小 – 大
• 「小」のテーブルを駆動表としたNested Loopsが最適
• 大 – 大
• HashかMerge Sort
41
結合のアルゴリズム
• DBMS毎の制御
• 結合のアルゴリズムはオプティマイザによって自動的に選択されます。
• しかし、DBMSによってはヒント句によって制御が可能です。
• Oracle:ヒント句によって制御可能
• SQL Server:ヒント句によって制御可能
• DB2:制御不可
• PostgreSQL:ヒント句やパラメータで制御可能
• MySQL:アルゴリズムがNested Loopsのみ
42
結合のまとめ
• 結合のまとめ
• 結合のアルゴリズムは主に3つある。
• DBMSによってサポートされているものが異なる
• ヒント句によって結合のアルゴリズムの制御も可能
• どこまでできるかはDBMSによって異なる
• 結合を速くするには
• 駆動表を小さくする
• 内部表の結合キーにインデックスが存在する
の2つを意識する
43
実行計画の読み方
様々なSQL文の実行計画
44
実行計画の読み方
• 実行計画の読み方の基本
• 複雑なSQL文の場合、実行計画は階層構造になっています。
• SQLの実行の順序としては、内側の「->」のマークの個所から実行され
ます。
• 同じ階層の場合は、上から順に実行されます。
• 最終的なコストは、それぞれのコストの合計値になります。
• 基本的にはコストが低いほうがパフォーマンスが高くなると考えて良
いが、コストの値が直接速度に比例するわけではない。
45
実行計画の読み方
• Nested Loopsの場合
• Nested Loopの横のコストは、1と2のそれぞれのコストの合計となる。
• 最初にレコード件数の少ないテーブルを絞り込み、そのあとに結合を
行う。
46
1. tableBのレコードを絞り込み
2. tableAのレコードと結合
実行計画の読み方
• Hashの場合
47
1. tableBのレコードを絞り込み
2. tableAのレコードを絞り込み
3. Hashによる結合
実行計画の読み方
• Sort Mergeの場合
48
2. tableBをキー順にスキャン
結合キーが一致したら結合
1. tableAをキー順にスキャン
演習問題
様々なSQLの実行計画を読み解いてみましょう
49
演習問題
• 演習問題
• 以下のSQL文の実行計画を確認してみましょう。
• 実行計画の結果から何が分かるか考察してみましょう。
50
-- ORDER BY ①
SELECT * FROM tableB
WHERE a_id BETWEEN 1 AND 100
ORDER BY b_id;
-- ORDER BY ②
SELECT * FROM tableB
WHERE a_id BETWEEN 1 AND 100
ORDER BY a_id;
-- ORDER BY ③
SELECT * FROM tableB
WHERE a_id BETWEEN 1 AND 100
ORDER BY a_id DESC;
演習問題
• 演習問題
51
-- LIMIT
SELECT * FROM tableB
WHERE a_id BETWEEN 1 AND 100
LIMIT 50;
-- ORDER BY & LIMIT
SELECT * FROM tableB
WHERE a_id BETWEEN 1 AND 100
ORDER BY b_id
LIMIT 50;
演習問題
• 演習問題
52
-- GROUP BY
SELECT value, count(*)
FROM tableB
GROUP BY value;
-- GROUP BY & HAVING
SELECT value, count(*)
FROM tableB
GROUP BY value
HAVING count(*) > 100;
演習問題
• 演習問題
53
-- GROUP BY & ORDER BY
SELECT value FROM tableB
GROUP BY value
ORDER BY value;
-- WHERE & GROUP BY
SELECT value, count(*)
FROM tableB
WHERE b_id < 1000
GROUP BY value;
演習問題
• 演習問題
54
-- EXISTS ①
SELECT * FROM tableA a
WHERE EXISTS (SELECT * FROM tableB
WHERE a_id = a.a_id AND value = 1
AND a_id BETWEEN 1 AND 200);
-- EXISTS ②
SELECT * FROM tableB b
WHERE a_id BETWEEN 1 AND 200
AND value = 1
AND EXISTS (SELECT * FROM tableA
WHERE a_id = b.a_id);
演習問題
• 演習問題
55
-- EXISTS ③
SELECT * FROM tableA a
WHERE EXISTS (SELECT * FROM tableB
WHERE a_id = a.a_id
AND value = 1);
-- EXISTS ④
SELECT * FROM tableB b
WHERE value = 1
AND EXISTS (SELECT * FROM tableA
WHERE a_id = b.a_id);
演習問題
• 演習問題
56
-- INを使ったサブクエリ ①
SELECT * FROM tableA
WHERE a_id in (SELECT a_id FROM tableB
WHERE value = 1
AND a_id BETWEEN 1 AND 200);
-- INを使ったサブクエリ ②
SELECT * FROM tableB b
WHERE a_id BETWEEN 1 AND 200
AND value = 1
AND a_id IN (SELECT a_id FROM tableA);
演習問題
• 演習問題
57
-- インラインビューを使用したサブクエリ
SELECT * FROM tableA a
INNER JOIN (SELECT value, max(a_id) a_id
FROM tableB
GROUP BY value) b
ON a.a_id = b.a_id;
参考資料
実際の検証結果
※必ずしも全く同じ結果になるとは限りません
58
参考資料
• ORDER BY ①
59
参考資料
• ORDER BY ②
60
参考資料
• ORDER BY ③
61
参考資料
• LIMIT
62
参考資料
• ORDER BY & LIMIT
63
参考資料
• GROUP BY
64
参考資料
• GROUP BY & HAVING
65
参考資料
• WHERE & GROUP BY
66
参考資料
• GROUP BY & ORDER BY
67
参考資料
• EXISTS ①
68
参考資料
• EXISTS ②
69
参考資料
• EXISTS ③
70
参考資料
• EXISTS ④
71
参考資料
• INを使ったサブクエリ ①
72
参考資料
• INを使ったサブクエリ ②
73
参考資料
• インラインビューを使用したサブクエリ
74
統計情報
75
統計情報
• 統計情報
• DBMSが管理する統計情報には様々なものがあります。
• 各テーブルのレコード数
• 各テーブルの列数と列のサイズ
• 列値の値の個数
• 列値の分布
• 列内のNULLの数
• インデック情報
などがあります。
76
統計情報
• 統計情報の更新方法
• 最適な実行計画は統計情報をもとに決定しています。
• DBMSが定期的に自動で取得する場合が多いが、大量のデータを更新し
た直後などは、手動で取得を行う必要があります。
77
DBMS コマンド
Oracle
EXEC
DBMS_STATS.GATHER_TABLE_STATS(OWNN
AME => スキーマ名, TABNAME => テーブル名)
SQL Server UPDATE STATISTICS テーブル名
DB2 RUNSTATS ON TABLE テーブル名
PostgreSQL ANALYZE テーブル名
MySQL ANALYZE TABLE テーブル名
統計情報
• 統計情報の重要性
• 統計情報が実行計画にどのように影響するかを確認してみます。
• 1レコードしか存在しないテーブルに対して実行計画を取得していま
す。
• その場合取得行数は1となっています。
78
統計情報
• 統計情報の重要性
• 次にレコードを100000件インサートし、実行計画を取得しない状態で
再度実行計画を取得してみます。
• すると2件というおかしな件数になっているのが分かります。
79
統計情報
• 統計情報の重要性
• analyzeコマンドを実施し、最新の統計情報を取得して再度実行計画を
取得した場合は、正しい結果になっているのが分かります。
80
統計情報
• 統計情報の重要性
• 以上の結果からわかるように、正しい統計情報が取得できないとオプ
ティマイザが求めた実行計画が最適でない状況になることがあります。
• 速度が遅い場合や大量のデータを扱う場合には統計情報を意識するよ
うにしましょう。
81
実行計画と統計情報のまとめ
• SQLが遅いと感じたら
• 実行計画を確認し、インデックスが適切に使用されているかどうかを
確認しましょう。
• 結合を使ったSQLが遅いと感じたら
• 実行計画を確認し、どのアルゴリズムが使用されている確認しましょ
う。
• 結合を速くするには、結合する側のテーブルの行数を少なくし、結合
される側のテーブルの結合キーにインデックスを使用することです。
• 大量のデータを更新したら
• 統計情報も最新に更新しましょう。
82
参考資料
Oracleの場合の実行計画
83
Oracleの実行計画
• Oracleの実行計画
• Oracleの場合、実行計画を確認する際に複数のオプションが選択でき
ます。コマンド実行後にSQL文を実行すると、実行計画の確認ができ
ます。
84
AUTOTRACEオプション 内容
set autotrace on explain 実行結果と実行計画を表示。
set autotrace on statistics 実行結果と統計情報を表示。
set autotrace on 実行結果と実行計画と統計情報を表示。
set autotrace traceonly 実行計画と統計情報を表示。
set autotrace off autotraceをオフにする。
Oracleの実行計画
• テーブルフルスキャン
85
SQL> SELECT * FROM tableA;
Oracleの実行計画
• インデックススキャン
86
SQL> SELECT * FROM tableA WHERE a_id = 1;
Oracleの実行計画
• インデックスオンリースキャン
87
SQL> SELECT a_id FROM tableA WHERE a_id = 1;
Oracleの実行計画
• Nested Loops
88
SQL> SELECT * FROM tableA a INNER JOIN tableB b ON a.a_id = b.a_id
WHERE b.value = 1 AND b.a_id = 10;
Oracleの実行計画
• Hash Join
89
SQL> SELECT * FROM tableA a INNER JOIN tableB b
ON a.a_id = b.a_id WHERE b.value = 1;
Oracleの実行計画
• HAVINGとWHEREの比較
• WHEREの場合
90
SQL> SELECT value, count(*) FROM tableB
WHERE value = 30
GROUP BY value;
Oracleの実行計画
• HAVINGとWHEREの比較
• HAVINGの場合
91
SQL> SELECT value, count(*) FROM tableB
GROUP BY value
HAVING value = 30;
Oracleの実行計画
• UNIONとUNION ALLの比較
• UNIONの場合
• ソートの処理が入る。
92
SQL> SELECT a_id FROM tableA
UNION
SELECT a_id FROM tableB;
Oracleの実行計画
• UNIONとUNION ALLの比較
• UNION ALLの場合
• ソートの処理はない。
93
SQL> SELECT a_id FROM tableA
UNION ALL
SELECT a_id FROM tableB;

2018年度 若手技術者向け講座 実行計画