
Oracle SQLチューニング(第40回)「照会パターンに合うようにSQL実行計画を分岐すること」(2/2回)
今回は、「照会パターンに合うようにSQL実行計画を分岐すること」の2回目です、それでは、早速、始めましょう。
11.2 SQLの照会パターン別の実行計画を分岐しよう
11.2.1 色々な条件を持つSQLの実行計画分岐すること
それでは前回調べてみた内容を基に下の性能問題SQLの問題点を把握して、SQLの性能を改善してみることにしよう。
[性能問題SQL]
[インデックス情報]
[性能問題SQL]は特定使用者(USERID)のデータを抽出する時、検索条件にNAMEまたはEMAIL条件が追加で照会される。 [性能問題SQL]は多くの使用者が同時に使うSQLで、特定使用者(USERID)で照会される時データ件数が多く、USERID条件だけ利用してインデックスを照会される場合I/O処理量がとてもたくさん発生します。
したがって状況によってUSERIDと追加条件で構成された結合インデックスを利用して照会になるようにしてこそ効率的でしょう。 しかし[性能問題SQL]はNAMEとEMAILに入力される値により照会パターンが違わなければならないにも関わらず、一つのSQLで作成されています。 その上ヒントによって実行計画まで固定されているので特定パターンの場合、非効率的に実行されています。 例えば、画面でNAME (:B2 IS NULL)で検索しないでEMAILで問い合わせる場合、該当SQLのインデックスはIDX_TB_ADDR_01インデックスでないIDX_TB_ADDR_02インデックスを利用して処理してこそ効率的なのですが、IDX_TB_ADDR_01インデックスを選択するようにヒントが適用されているので非効率的な実行になります。
結局照会条件であるEMAIL条件はテーブル アクセス後Filter Predicateで処理されることによって非効率が発生することになります。 その上実際の業務でNAMEよりはEMAIL条件で、たくさん実行されるならば深刻な性能問題を起こすことになります。
したがってNAMEとEMAIL条件に対して照会パターンを分析した後、各パターン別で最適な実行計画で実行できるようにSQLを再作成しなければなりません。
前回調べてみた通り、SQLをパターン別で分離した後に、照会される条件に対する値をチェックした後、最も有利なSQLを選択して実行するようにしなければならないのです。 もし、あえて一つのSQLで作成するべきだとすれば[改善後SQL]のようにUNION ALLを利用して、実行計画を分離させる方法もあります。 UNION ALLを使った方法は分離しなければならない条件が少ないならばなかなか良い方法ですが、条件が多いならばSQLが長くなってパッシング時負担となる恐れがあるという短所があります。
[改善後SQL]
今までSQLが色々な照会パターンを有しているにも関わらず、一つのSQLで実現する場合、発生しうる性能問題に対して簡略に調べてみました。 最近ではLIKE ‘%’方式のSQLで色々な照会パターンを処理する方法を使うよりは、プログラム(XMLなど)で照会条件をダイナミックに追加して、照会パターン別でそれぞれ他のSQLが実行されるように実現する場合が多くあります。 この方法はLIKE ‘%’よりはるかに改善された方法ですが、Optimizerの誤った解釈で深刻な性能問題が発生したり、誤ったヒント(すべてのケースに適用されるヒント適用)を適用して性能問題が発生する場合は相変らず存在するので、照会パターン別で実行計画を分離することができるようにSQLを作成するのを検討しなければなりません。 特にプログラムにヒントを付与する場合、それぞれの条件に適合するように適用しなければならないという点を必ず覚えておいてください。
それでは前回調べてみた内容のように色々な照会パターンを持つSQLだけ実行計画分離が必要なのでしょうか? そうではありません。 LIKE,NVLなどを使わないで、条件が固定されたSQLも条件値の範囲や照会値のLENGTHによりインデックスを実行するべきか、でなければFull Table Scanをするべきかを決めなければならない場合があるためです。
実行計画分離が必要な場合を整理して見るならば、以下の二種類が代表的なケースとなります。
- 色々な照会パターン処理を一つのSQLで作成した場合(LIKE,NVL,DECODEなど)
- 照会変数値の範囲により他の実行計画が必要な場合
これから、条件は固定されるが、照会変数値の範囲により実行計画分離が必要な場合に対して調べてみることにしましょう。
11.2.1.1 変数値の範囲によりSQLを分離しよう
“変数値の範囲にともなう実行計画分離”はSQLを問い合わせる変数値によって抽出されるデータ件数の差が大きい場合に使うことができます。 例えば、抽出されるデータ件数が少ない場合はインデックス スキャンで実行されるようにして、データが多い場合はFull Table Scanで実行されるように誘導することを言います。 もし、変数値の範囲により最小限二種類の実行計画に分かれなければならない場合ですが、これを一つのSQLで作成したとすれば、値の範囲に関係なく一つの実行計画と解釈されるので、特定変数値の範囲に対しては非効率が発生することになります。 理解を助けるために例をあげるとすると、以下のような照会パターンになります。
REG_DATEが顧客の加入日カラムならば、:B1 ~:B2間に加入したすべての顧客を抽出しようとするSQLです。 もし、CUST (顧客テーブル)テーブルに顧客が一日平均500人が加入すると仮定すると、加入日者の範囲によりデータ抽出件数は多くの差が発生することで、インデックスを使うかFull Table ScanをするかによりSQL実行時性能偏差も大きく発生するでしょう。
したがってREG_DATEに対する照会パターンが平常時には1日照会が大部分だが、最低1ヶ月に一度は最近一年間のデータを抽出するべきだとすれば、抽出されるデータ件数は下記のように大きい差が発生します。
1日照会 : 500件抽出
1年照会 : 500件* 365日= 182,500件抽出
平常時照会されるSQLの実行は1日データを問い合わせるのでデータ量が少ないのでインデックス スキャンで実行されることが効率的です。 しかし、1ヶ月に一度照会される1年分データを問い合わせる時にはデータ量が多いので、インデックス スキャンで実行される場合、急激な性能低下が発生することになるでしょう。 このように変数値の範囲により、SQL性能の偏差が大きい場合にも効率的なSQL実行になるべく実行計画を分離させる改善案が必要となります。
照会変数値によりSQLを分離しなければならない場合の大部分は照会条件が日データの範囲検索である場合が多いですが、日データの範囲検索ではなくても、照会パターン別で分けなければならない場合も存在するのでWhere節の照会パターンを綿密に検討しなければなりません。
以下の事例を通じていWhere節照会変数値の範囲により実行計画を分離しなければならないSQLなのかを確認して、改善案を導き出して適用する方法に対して調べてみることにしましょう。
[性能問題SQL]
[性能問題SQL]で抽出しようと思うデータは特定月(GOJI_YYYYMM)に特定支社(BRCH_CD)のデータのうちにSN_NO照会値の範囲にあるデータです。 SQLは先に支社条件に満足するデータを抽出した後(TB_G10テーブル),特定月に生成された(GOJI_YYYYMM条件)データ(TB_M02テーブル)とHash Joinを実行します。 そしてこの対象をTB_G30テーブルとNested Loops Joinを実行した後SN_NOに範囲に該当するかをチェックして、最終データを抽出するようにヒントが適用されています。 ところで[性能問題SQL]に追加されているヒントは非効率を有しています。 どんな非効率を有しているのでしょうか? 次の照会パターン[1],[2]を通じて確認してみましょう。
(1)照会パターン[1]. SN_NO照会条件が特定値である場合
(2) 照会パターン[2]. SN_NO照会条件が全体範囲である場合
照会パターン[1]と照会パターン[2]のトレース結果を見れば照会パターン[1]は抽出結果が0件、照会パターン[2]は3,338件です。 照会パターン[1]の場合TB_G30テーブルと結合処理した以降で0件になるのを確認することができます。 一方、照会パターン[2]の場合は0件でない3,338件のデータが抽出されますが、TB_G30テーブル結合時IX_TB_G30_03インデックスをアクセスする時データ差が発生しています。
TB_G30テーブルはTB_M02テーブルやTB_G10テーブルと結合条件だけ持って結合をする場合、全部結合に成功しなければならないという特徴を有していました。 これはTB_G30テーブルは結合条件だけでは先行テーブルで抽出されたデータ件数が減らないという意味です。
すなわち、IX_TB_G30_03インデックス構成カラムのうち定数条件で入力されるカラムであるSN_NOの照会値により結果件数が大きく変わるということです。
したがって照会パターン[1]は先行テーブルで抽出された3,338件中SN_NO照会値に該当するデータが存在しなくて最終抽出件数が0件になったのです。 一方、照会パターン[2]は先行テーブルで抽出されたすべてのデータがSN_NO照会範囲に該当するので3,338件が抽出されました。
ここでSN_NOカラムはNUM_DISTINCT値が高くてEQUAL照会時平均2~3件を抽出するので、SN_NO条件がEQUALで照会されるならばTB_G30テーブルを先に読んでSN_NOカラムで構成されたインデックスを活用すればより効率的なSQL実行が可能でしょう。
トレース結果を見れば、照会パターン[1]と照会パターン[2]のSQLはSN_NO照会条件の範囲により実行計画が違うように実行されなければならないと判断されます。 それなら先にSN_NOカラムに対する照会値が持つパターンを確認する必要があります。 Oracle 10g以上ではV$SQL_BIND _CAPTUREとDBA_HIST_SQLBINDビューを利用してBind値を問い合わせることができます
DBA_HIST_SQLBINDビューは過去にSQL実行時使ったBind値の履歴を有しているので、SN_NOカラムの照会パターン(定数値)を確認するためにDBA_HIST_SQLBINDビューを問い合わせてSN_NOの照会値を確認してみました。
DBA_HIST_SQLBINDビューを問い合わせた結果を見れば、SN_NO照会値のパターンは大きく二種類に分かれるのを確認することができます。 全体データを問い合わせたり、特定値で問い合わせることです。 それなら[性能問題SQL]に対する改善案はSN_NO照会値のパターンにより実行計画分離が最も適合するとみられます。 なぜなら、SN_NOに対する条件が特定値で照会される場合には平均2~3件だけ抽出されるので、TB_G30テーブルを先にアクセスすることが効率的なためです。
結論的にSN_NO値を問い合わせる変数値は:B67 (開始値)と:B68 (終わり値)ですが、二つの変数値が同じ場合には特定値で照会されて、二つの変数値がそれぞれ違うのは特定値でない全体を問い合わせる場合です。 したがって二種類の照会パターンにより、適切に実行計画が分離できるようにUNION ALLでSQLを再作成してみましょう
[改善案導き出し]
[照会パターン[1]の改善後トレース結果]
[照会パターン[2]の改善後トレース結果]
[改善案導き出し]でUNION-ALL構文で再作成したSQLを見れば、分離したSQLをもう一度インライン ビューで包んだ後インライン ビュー外で変数値をチェックする方法でSQLを作成しました。 その理由は以下のような方式でSQLを作成する場合、11gで該当SQLに対して実行計画を分離する時JOIN FACTORIZATIONによって予期できない性能問題が発生する場合があるためです。
UNION ALLで実行計画を分離した後、照会パターン[1]の値に対しては、効率的な実行になりました。 しかし照会パターン[2]でUNION ALLの下の部分を実行する時、TB_G10,TB_M02頭テーブルをHash Joinで処理した後、TB_G30テーブルにアクセスしてこそ効率的な実行になるのですが、意図した通りにはなりませんでした。
すなわち、TB_G10テーブルが持つ支社コード(BRCH_CD)条件で先にデータを減らすべきなのに、これをSQLの一番最後に実行することによって非効率が発生したのです。 このような現象はOptimizerが実行計画を樹立する時、性能を最適化するためにUNIONまたはUNION ALLを使うSQLの内容のうち共通で使うテーブルをビューの外で分離させて重複使用を避けるJOIN FACTORIZATIONを実行するためです。 意図はよかったのですが、かえって既存SQLより性能が低下してしまいました。 したがってJOIN FACTORIZATIONが実行されないように[改善案導き出し]と一緒にもう一度インライン ビューで包み込む方式でSQLを再作成しました
また他の方法で以下のSQLのように11gで提供するJOIN FACTORIZATIONを制御するヒントを使ってSQLを実行したのです。
今回は色々な照会パターンを持つSQLが一つの実行計画で固定される場合、特定照会パターンで実行時発生する性能問題に対して調べてみました。 これと関連した性能問題に対してLIKE ‘%’やNVLを含むSQLが各照会パターンに合うように効率的な実行になるべくSQLを再作成して解決する事例と、Where節条件のうち変数値の範囲により実行計画を分離した事例を調べてみました。
実務では前で説明したSQLとはまた他の方式で性能問題を起こルはずです。 しかしSQLの業務的な性格を把握してSQLが持つ照会パターンを明確に分析するならば、性能問題に対する改善方法を導き出すのは難しくないでしょう。
今回は、ここまでになります。いかがでしたでしょうか?直からは、「ROWNUMの理解と関連SQL性能問題の理解」と題しまして、6回シリーズでお送りいたします。ご期待ください。では、See you ^^