本書では、GridDBのSQLのチューニングについて説明します。
本書は、GridDBでSQLを用いたシステム開発を行う開発者の方を対象としています。
本書は、以下のような構成となっています。
最適化のルールやチューニングなどについては、GridDBに特化した特徴的な点を主に説明します。
SQLのチューニングは、一般的なデータベースシステムと同様に以下のような手順で行います。
STEP1 遅いクエリの確認
STEP2 プランの取得
STEP3 クエリのチューニング
STEP4 クエリの再実行
STEP1からSTEP3について詳細を説明します。
システムで実行しているクエリの中で、時間がかかっている遅いクエリを特定します。
実行時間がかかった遅いクエリは、そのクエリと実行時間などの情報をイベントログに出力することができます。 これにより、アプリケーションから実行された複数のクエリの中から、ボトルネックとなっているクエリを特定することができます。
遅いクエリの確認の手順は以下の通りです。
GridDBノードに、スロークエリの実行時間の閾値とイベントログに出力するクエリ文字列のサイズ上限のパラメータがあります。
パラメータ | 意味 | デフォルト値 |
---|---|---|
/sql/traceLimitExecutionTime | スロークエリをイベントログに残す実行時間の下限値(単位:秒) | 300s |
/sql/traceLimitQuerySize | スロークエリに残るクエリ文字列のサイズ上限(単位:バイト) | 1000 |
デフォルト値から変更する場合は、次の2つの方法のいずれかで設定します。
[メモ]
スロークエリのログを確認します。確認方法は次の2つがあります。いずれかの方法でスロークエリを特定してください。
確認方法 | 内容 |
---|---|
オンラインで最新の情報を確認する | 運用ツールgs_logsのオプション–slowlogsを実行すると、スローログの情報を表示します。 ・表示は、最新のイベントログファイルの情報のみです。 ・イベントログファイルは、ファイルサイズが閾値を超えた場合、または日にちが変わった場合に切り替わります。切り替わると、gs_logsでは古いイベントログファイルの内容は表示されません。 |
イベントログファイルを直接確認する | ノードのイベントログファイルを直接確認します。 スロークエリのログは「SQL_LONG_QUERY」をキーワードにしてファイルから抽出してください。 |
[メモ]
STEP1で特定したスロークエリを実行して、クエリのプラン(グローバルプラン)を取得します。
プランの取得には運用ツールgs_shを用います。EXPLAIN ANALYZE構文でクエリを実行し、サブコマンドgetplantxtでプランを取得します。
(1) クエリを「EXPLAIN ANALYZE」構文で実行する
gs[public]> sql EXPLAIN ANALYZE select * from table1, table2 where table1.value=1 and table1.id=table2.id ;
検索を実行しました。 (19 ms)
(2) プランを取得する
gs[public]> getplantxt
Id Type Input Rows Lead time Actual time Node And more..
--------------------------------------------------------------------------------------------------------------------
0 SCAN - - 30 30 192.168.15.161:10001 table: {table1}
1 SCAN 0 3000 21 21 192.168.15.161:10001 table: {table1, table2} INDEX SCAN JOIN_EQ_HASH
2 RESULT 1 14 0 0 192.168.15.161:20001
GridDBでは、SQLに対して以下のように処理を行います。 ノードはクエリを構文解析しプランを生成します。プランは実行単位であるタスクごとのプランから構成されており、各ノードは割り当てられたタスクを実行します。
プラン表示のサブコマンドgetplantxtでは、このタスク単位のプランが1行ずつ表示されます。タスクの出力が次のタスクの入力になります。
「(2) プランを取得する」の実行例で、具体的にプランの表示を説明します。
WHERE句の絞り込み条件のスキャン処理や、テーブルのジョインのためのスキャン処理では、索引の利用有無によって大きく性能が変わる場合があります。 また、テーブルのジョインの場合は、結合順序などによっても大きく性能が変わります。 よって、これらをポイントにクエリのチューニングを行ってください。
(1) プランの分析
プランを分析して、索引の利用有無や意図したとおりに動作しているかを確認します。 プランの詳細はSQLのプランをご参照ください。
索引の利用有無の確認方法
(2) チューニング
プランの分析による問題点に応じて、次のような方法でチューニングを行います。
例) テーブルのジョインを行うクエリで、チューニングの例を説明します。
table1とtable2をカラムvalueの値で結合するクエリについて、プランを取得します。
gs[public]> EXPLAIN ANALYZE select * from table1, table2 where table1.value=0 and table1.value=table2.value;
検索を実行しました。 (13 ms)
gs[public]> getplantxt
Id Type Input Rows Lead time Actual time Node And more..
-------------------------------------------------------------------------------------
0 SCAN - - 20 20 192.168.15.161:10001 table: {table1}
1 SCAN - - 9 9 192.168.15.161:10001 table: {table2}
2 JOIN 0,1 10000,3000 891 891 192.168.15.161:20001 JOIN_EQ_HASH
3 RESULT 2 32 2 2 192.168.15.161:20001
このプランでは、table1とtable2をそれぞれスキャンして、索引を使わずにジョインの処理を行っています。
テーブルの索引情報を確認すると、カラムvalueに索引が付いていなかったため、索引を作成します。
同じクエリを実行してプランを取得します。
gs[public]> EXPLAIN ANALYZE select * from table1, table2 where table1.value=0 and table1.value=table2.value;
検索を実行しました。 (7 ms)
gs[public]> getplantxt
Id Type Input Rows Lead time Actual time Node And more..
--------------------------------------------------------------------------------------------------------------------
0 SCAN - - 20 20 192.168.15.161:10001 table: {table1}
1 SCAN 0 10000 80 80 192.168.15.161:10001 table: {table1, table2} INDEX SCAN JOIN_EQ_HASH
2 RESULT 1 32 3 3 192.168.15.161:20001
プランID2で「INDEX SCAN」と表示されており、索引を使用したジョイン処理にプランが変わっています。
WHERE句の絞り込み条件に一致するデータを探すスキャン処理では、テーブルの全ロウにアクセスする「フルスキャン」よりも、 テーブルの索引を用いてアクセスする「索引スキャン」の方が、多くの場合に高速になります。
特に、WHERE句の絞り込み条件がテーブルのロウ数に対してヒット率が小さくなるような、データをより絞り込める条件ほど索引スキャンの効果が高くなります。
GridDBのSQL最適化において、スキャン処理で使用する索引の選択ルールを説明します。
絞り込み条件に指定された演算子や式によって、ルールが異なります。
AND
基本的に、絞込み条件のカラムに索引が設定されている場合は先頭の索引を使用します。
例)
a>1 AND b=2 (aとbに索引あり)
ただし、例外的に先頭の索引を使用しない場合もあります。例を以下に示します。
定数falseのOR条件”OR false”を付けると、索引を使用しません。
スキャンで索引を使用したくない場合は、この構文を指定してください。
例) 「a>1 AND b=2」で、aの索引を使用したくない場合
(a>1 OR false) AND b=2 (aとbに索引あり)
例) 「a>1 AND b=2」で、aとbの索引を使用したくない場合
(a>1 AND b=2) OR false (aとbに索引あり)
a>1 AND b=2 OR false (aとbに索引あり)
AND条件内にORやINが含まれている場合は、索引利用可能な先頭の式(最も左側の式)でのみ索引を使用します。
例)
(a=1 OR a=3) AND b=2 (aとbに索引あり)
a IN (1,3) AND b=2 (aとbに索引あり)
例)
(a=1 OR a=3 OR false) AND b=2 (aとbに索引あり)
(a IN (1,3) OR false) AND b=2 (aとbに索引あり)
なお、同一カラムに対する条件が重複する場合は、条件をマージして索引を使用します。
例)
a>1 AND a<=4 AND a<=3 (aに索引あり)
OR
ORの場合は、絞込み条件に指定したカラムに、すべて索引が設定されている場合にのみ索引を使用します。
例)
a>1 OR b=2 (aとbに索引あり)
例)
a>1 OR b=2 (bに索引あり)
比較演算子の式
比較演算子の値にカラム単独の式と定数式を用いる場合のみ、索引を使用します。
例)
a>10*1000-1 (aに索引あり)
例)
a+1>10*1000-1 (aに索引あり)
例)
a>b (aとbに索引あり)
IN、BETWEEN
INとBETWEENは、ANDとORと比較演算子を組合せた式とみなしたうえで、上記のルールを適用します。
例)
a IN (1,2) → a=1 OR a=2 (aに索引あり)
a BETWEEN 1 AND 2 → a>=1 AND a<=2 (aに索引あり)
[メモ]
索引スキャンで使用する索引は、NoSQLまたはNewSQLインタフェースで作成したTREE索引、または、主キーに自動的に設定されるTREE索引のみです。 NoSQLインタフェースで作成したHASH索引や空間索引は使用しません。
SQL構文のヒント「NoIndexScan」を用いると、索引を使用しないスキャンが指定できます。ヒントの詳細は「ヒント句」をご参照ください。
GridDBのSQL最適化において、スキャン処理で使用する複合索引の選択ルールを説明します。
絞り込み条件に指定されたカラムや演算子によって、索引を使用する範囲が異なります。 複合索引を構成するカラムの先頭よりAND条件で連続して存在するカラムの条件までを複合索引として使用します。
[メモ]
例)
where col1 = 1 and col2 = 1 and col3 = 2 (col1,col2,col3の複合索引あり)
where col1 = 1 and col2 > 1 and col3 < 2 (col1,col2,col3の複合索引あり)
where col1 = 1 and col2 = 1 (col1,col2,col3の複合索引あり)
where col1 = 1 and col3 = 2 (col1,col2,col3の複合索引あり)
where col2 = 1 (col1,col2,col3の複合索引あり)
where col1 = 1 and (col3 >= 0 and col3 < 10) and col2 = 1 (col1,col2,col3の複合索引あり)
where col1 = 1 and (col3 = 0 or col3 = 1) and col2 = 1 (col1,col2,col3の複合索引あり)
where (col1 = 0 or col1 = 1) and col2 = 1 (col1,col2,col3の複合索引あり)
where col1 = 1 and (col3 >= 0 and col3 < 10) (col1,col2,col3の複合索引あり)
where col1 = 1 and (col3 >= 0 and col3 < 10) and ABS(col2) = 1 (col1,col2,col3の複合索引あり)
GridDBのSQL最適化において、複数テーブルをジョインする処理に関する次のルールを説明します。
索引を使用しないジョイン処理においては、ジョイン順序やジョイン演算方法の違いが性能に大きく影響します。
結合処理において、最初にアクセスするテーブルを駆動表、次にアクセスして結合するテーブルを内部表といいます。
ジョインの順序(駆動表と内部表)は次のルールで決まります。
2つのテーブルの結合の場合、定数の等価絞込み条件があるテーブルが駆動表になります。
例)
t1.a=t2.x AND t2.y=1
3つ以上のテーブルの結合の場合、結合や絞込み度合いの強さによって順序が決まります。
結合度合いの強いテーブルは、連続してジョインするような順番になります。
例)
FROM A, B, C
例)
FROM A, B, C WHERE A.x=C.z AND C.z=B.y
例)
FROM A, B, C WHERE A.x>=C.z AND C.z>=B.y AND B.y=A.x
絞込み度合いの強いテーブル(該当データ数が少ないと推定されるもの)ほど、ジョインの順番が先になります。
例)
FROM A, B, C WHERE A.x=C.z AND C.z=B.y AND B.x=1
例)
FROM A, B, C WHERE A.x=C.z AND C.z=B.y AND A.x IN (1, 2) AND B.x IN (1, 2, 3)
[メモ]
ジョイン処理で索引を使用するかどうかのルールを説明します。
次の5つのすべてを満たす場合に索引を使用します。
駆動表側に等価絞込み条件がある
例)
t1.a=t2.x AND t2.y=1 (aに索引あり、駆動表はt2、内部表はt1)
例)
t1.a=t2.x AND t2.y>1 (aに索引あり、駆動表はt2、内部表はt1)
第一結合条件が等価結合条件である
例)
t1.a>t2.x AND t1.b=t2.y AND t2.z=1 (aに索引あり、駆動表はt2、内部表はt1)
第一結合条件の内部表側の等価結合条件カラムに索引が設定されている
例)
t1.a=t2.x AND t2.y=1 (aに索引なし、駆動表はt2、内部表はt1)
INNER JOINである
NoIndexJoinヒントで無効化されていない
[メモ]
ジョインの構文が索引の適用ルールに当てはまる場合、索引を使用してジョインを行います。 ジョインで使用する索引を選択するルールを説明します。
基本的に、スキャンの索引の選択ルールと同様です。 カラムに設定されている索引はすべて記述順に使用します。
すべての索引を使用しない場合もあります。例を以下に示します。
絞込み条件のカラムに索引が設定されている場合は先頭の索引を使用します。
例)
t1.a=t2.x AND t1.b>t2.y AND t2.z=1 (aとbに索引あり、駆動表はt2、内部表はt1)
OR条件(A OR B)において、Bがfalse定数の場合、Aの索引は使用しません。
例)
t1.a=t2.x AND (t1.b=t2.y OR false) AND t2.z=1 (aとbに索引あり、駆動表はt2、内部表はt1)
ジョインの演算方法には次の3つの種類があります。
ジョインの演算方法 | 説明 |
---|---|
ハッシュジョイン | 駆動表の結合キーをハッシュ関数にかけてメモリ上に一時的なテーブルを作り、内部表のハッシュ値と一致するか比較して結合する方法です。 |
ソートマージジョイン | 結合する2つのテーブルを結合キーでソートして、それらを順に比較して結合する方法です。 |
ネステッドループジョイン | 駆動表の結合キーの値を元に、結合条件に合致する内部表のデータを探して結合する方法です。 |
速度が速いのは、ハッシュ、ソートマージ、ネステッドループの順です。
ジョインの第一結合条件の種類によって、これらの演算方法を選択します。
第一条件 | 選択する演算方法 |
---|---|
等価条件 | ハッシュまたはソートマージジョイン (ハッシュが選択されていても、メモリリソースの制限により、部分的にソートマージに切り替わることがあります。) |
大小条件 | ソートマージジョイン |
なし | ネステッドループジョイン |
ジョインの第一結合条件は、次の優先順で選択します。
単純カラム式を優先して第一結合条件とします。
例)
t1.a=abs(t2.x)+10 AND t1.b=t2.y
単純カラム式が複数ある場合は、先に記述されている式を第一結合条件とします。
例)
t1.a>t2.x AND t1.b=t2.y
実行計画を示すヒントをクエリに指定することで、SQL文を変えることなく実行計画を制御できます。
【注意事項】
ヒント機能で用いる用語は次のとおりです。
用語 | 説明 |
---|---|
ヒント句 | 実行計画を制御するための情報 |
ヒント | ヒント句を列挙したもの。実行計画を制御するクエリに指定する。 |
実行計画を制御するクエリのブロックコメントの中にヒントを記述します。ヒント用のブロックコメントは、 SQL文中の先頭のSELECT(INSERT/UPDATE/DELETE)句の直前または直後のみ記述できます。通常のコメントと区別するため、 ヒント用のブロックコメントは「/*+」で始めます。
ヒントの対象は、ヒント句の括弧内にオブジェクト名または別名で指定します。複数のオブジェクト名を指定する場合、 スペース、タブ、改行のいずれかで区切って指定します。
以下の例では、Leadingヒント句により、テーブル結合順序を指定しています。
/*+
Leading(t3 t2 t1)
*/
SELECT *
FROM t1, t2, t3
ON t1.x = t2.y and t2.y = t3.z
ORDER BY t1.x
LIMIT 10;
【メモ】
指定できるヒント句の一覧を次に示します。
分類 | 命令 | 説明 |
---|---|---|
並列化 | MaxDegreeOfTaskInput(上限数) | 1タスクへの入力の上限 |
MaxDegreeOfExpansion(上限数) | プランノードの展開数の上限 | |
スキャン方式 | IndexScan(テーブル) | 可能な場合はインデックススキャンを選択する |
NoIndexScan(テーブル) | インデックススキャンを選択しない | |
結合方式 | IndexJoin(テーブル テーブル) | 可能な場合はインデックスジョインを選択する |
NoIndexJoin(テーブル テーブル) | インデックスジョインを選択しない | |
結合順序 | Leading(テーブル テーブル[ テーブル …]) | 指定したテーブルを指定した順序で結合する |
Leading(( テーブル集合 テーブル集合 )) | 1つ目に指定したテーブル集合を外部表、 2つ目に指定したテーブル集合を内部表として結合する |
テーブル集合 = { テーブル or ( テーブル集合 テーブル集合 ) }
ヒント句の分類ごとに詳細を説明します。
並列化処理の制御を行います。
テーブルのスキャン方式を指定します。
結合方式を指定します。
テーブルのジョイン処理でどのような順番で結合するかを指定します。
(1) 結合順序のみ指定: Leading(テーブル テーブル[ テーブル …])
先に結合するテーブルから順にテーブル名または別名を指定します。この指定方式の場合、常にLeft-deep joinで結合されます。
(例1)
/*+ Leading(S R Q P) */
SELECT * FROM P,Q,R,S WHERE P.x = Q.x AND ...
(2) 結合方向を含めた指定: Leading((テーブル集合 テーブル集合))
テーブル集合 = { テーブル or (テーブル集合 テーブル集合) }
(1)のように結合順序のみを指定した場合、結合方向(外部表/内部表の別)が期待と異なる場合があります。 結合方向を固定したい場合は以下の書式を使います。
/*+ Leading((t1 (t2 t3))) */
SELECT ...
この書式では、括弧をネストして記述できます。括弧内の1つ目に指定したテーブル集合を外部表、 2つ目に指定したテーブル集合を内部表として結合されます。
(例2-1)
/*+ Leading(((P Q) R)) */
SELECT * FROM P,Q,R WHERE P.x = Q.x AND ...
(例2-2)
/*+ Leading((R (Q P))) */
SELECT * FROM P,Q,R WHERE P.x = Q.x AND ...
(例2-3)
/*+ Leading(((P Q) (R S))) */
SELECT * FROM P,Q,R,S WHERE P.x = Q.x AND ...
【メモ】
以下の場合は構文エラーとなります。
以下の場合はテーブル指定エラーとなります。
【メモ】
SQL最適化によってどのような演算や索引が選択されたかは、SQLのEXPLAIN ANALYZE文で確認することができます。
SQLの処理では、SQL構文を解析して最適化し、ジョインやソート・スキャンなどの複数の「タスク」という処理単位に分解してプラン(実行計画)を生成します。
タスクは、クラスタを構成するノードのいずれかひとつで実行され、タスク間でデータのやり取りをしながら並列実行します。
[メモ]
EXPLAIN ANALYZE文を実行すると、タスクのプランや実行時間などの情報を、タスク単位で1行ごとにJSON形式で出力します。
出力する主な項目は以下の通りです。
項目 | 説明 |
---|---|
id | プランID |
type | 処理の種類 |
inputList | 入力値となるプランのプランIDの列 |
profile/leadtime | 処理時間 |
profile/rows | 入力件数 |
profile/address | 処理を実行したノードのアドレスとポート番号 |
[メモ]
処理の種類は以下の通りです。
typeの値 | 説明 |
---|---|
GROUP | グルーピング演算 |
JOIN | ジョイン演算 |
LIMIT | 行単位件数フィルタ演算 |
SCAN | テーブルスキャン演算 |
SELECT | 選択演算(条件フィルタ・プロジェクション) |
SORT | ソート演算 |
UNION | 連結・集合演算 |
INSERT、UPDATE、DELETE | 各種コンテナ変更操作 |
DDL | DDL・DCL文相当 |
RESULT | 結果の保持 |
プランは、運用ツールgs_shで取得することができます。
例) クエリ「select * from table1, table2 where table1.value=0 and table1.id=table2.id」のプラン取得
gs[public]> EXPLAIN ANALYZE select * from table1, table2 where table1.value=0 and table1.id=table2.id;
検索を実行しました。 (11 ms)
gs[public]> getplantxt
Id Type Input Rows Lead time Actual time Node And more..
--------------------------------------------------------------------------------------------------------------------
0 SCAN - - 0 0 192.168.15.161:10001 table: {table1} INDEX SCAN
1 SCAN 0 0 2 2 192.168.15.161:10001 table: {table1, table2} INDEX SCAN JOIN_EQ_HASH
2 RESULT 1 0 0 0 192.168.15.161:20001
gs_shでサブコマンドgetplanjsonを実行すると、プランをJSON形式で出力することもできます。
[注意]
例) プランのJSON形式の例
{
"nodeList" : [ {
"cmdOptionFlag" : 65,
"id" : 0,
"indexInfoList" : [ 3, 3 ],
"inputList" : [ ],
"outputList" : [ {
"columnId" : 0,
"columnType" : "INTEGER",
:
:
}