
Oracle SQLチューニング Season2(第17回) 第3章 情報分析による性能改善事例
今回のSQLチューニング 2nd Season(第17回)は「SQL チューニングのための情報分析方法」の最終回として「情報分析による性能改善事例」について解説していきます。
3.4 情報分析による性能改善事例
ここまで、SQL性能改善のために必要な情報を把握するための方法について紹介してきました。
第17回となる今回のブログでは、先に紹介したSQLの実行情報を把握する方法、SQLを構成するObjectの構成情報を把握する方法、SQLの業務情報を分析する方法など、性能改善時に必要なすべての情報を簡単な事例を挙げながら解説していこうと思います。
■ 下記のような 日別販売量を抽出するクエリ を例に挙げてみましょう。
3.4.1 STEP1.SQL実行情報を分析する
上記のSQLの性能問題が提起された場合、SQLの実行情報を先に把握する必要があります。
実行計画を抽出する方法には、先に紹介した10046 Trace、DBMS_XPLAN、DBMS_SQLなどがありますが、ここではMS_XPLANを使用することにします。
■ DBMS_XPLAN情報を抽出するために必要なヒント /*+ gather_plan_statistics */ を追加して実行情報を抽出します。
実行計画を実行順に見ていくと、ID番号3->2->1->0の順に解釈することが可能です。
SALES_INテーブルをTABLE FULL SCANを利用してテーブルの総ブロックである4,441 Blockを全て読み込みました。
その後、全体の抽出件数のうちWhere条件であるWHERE time_id >= TO_DATE( :from_date , ‘yyyy-mm-dd’ ) AND time_id <= TO_DATE( :to_date , ‘yyyy-mm-dd’ )を満たすデータのみFilteringして327件を抽出し、抽出件数327件に対してTime_idでGroupingして最終抽出件数1件をFetchしました。
3.4.2 STEP2. Index構成情報の確認実行計画を見ると、OptimizerはSQLのデータを抽出するために
TABLE FULL SCANを選択しました。
Optimizerが選択した実行計画が最も効率的なのか、改善できる方向があるかどうかをここで考えてみる必要があります。
一番最初に考えなければならない部分は、TABLE FULL SCANが果たして効率的な方法であるのかどうか?を考えてみる必要があります。
当該SQLのWhere節のカラムにIndexが存在する場合、Index ScanとTABLE FULL SCANのどちらがより効率的なのかを調べてみることが必要です。まず、SQLの検索条件カラムにIndexが存在するかどうかについて確認してみましょう。
■ ここでは『 Index構成情報を把握する 』の回で活用したスクリプトを使って確認してみましょう。
当該テーブルのIndexを見てみると、当該カラムにはIndexは存在していないことを確認することができました。
3.4.3 STEP3.Where節条件カラムの効率確認
SALES_INテーブルに該当カラムのIndexが存在しないため、OptimizerはTABLE FULL SCANを選択しました。
もしそうであるならば、そのカラムにIndexが存在する場合、更に効率的なのかどうか?について考えてみましょう。
■ Indexを生成する前にいつもそのカラムの効率を確認する必要があります。
カラムの統計情報を基にカラムの効率を判断した場合、918843/1460 = 約629件でテーブル全体を全て照会するよりは効率的であると判断されます。その場合、そのカラムでIndexを生成することを考慮してみることができます。
Note. カラムの効率を判断する基準は、テーブル全体の件数/カラムNDV(Number of Distinct Value)でその効率を判断することができます。(本文で計算された値は、1日のデータが照会された場合に限る)。
3.4.4 STEP4. Bind Pattern分析
Index生成時に効率的であることをSALES_INテーブルの統計情報照会で確認しました。
では、TIME_IDカラムで照会する場合、常にIndex Scanが効率的なのでしょうか?
この質問に対する答えは、Bind Patternを把握した場合のみ正確な答えが可能です。
Bind値が入力されるTime_id条件に1日ではなく1ヶ月あるいはそれ以上のデータが照会されると、先に計算したカラムの効率性かどうかに大きなエラーが発生する可能性があるからです。
現在実行されたバインド値は、1日範囲のデータ値を抽出するものですが、常に1日間のデータだけを照会しているかどうか?については、バインド値のパターンを確認しなければ正確に知ることができません。
日付区間の検索条件は業務担当者が一番よく知っていますが、Oracleはdba_hist_sqlbind、dba_hist_snapshotを照会すると、特定期間中のBind Patternを照会することができます。
Bind Patternを見ると、日照会だけでなく月照会も行われ、合計2種類のBind Patternが存在することが分かりました。
月照会が実行された場合の効率は、先に日照会の効率を計算した値に約30を掛ける必要があります。
その値は約18,870で、Index生成に関して決して効率的ではない数値であることが分かりました。
3.4.5 STEP5.月次照会のFTS効率確認
Bind Patternから抽出された情報を通じて、日または月ごとに照会されるSQLであることを確認しました。
日別照会はIndexを生成することで解決が可能ですが、月別照会はIndex Scanの時に非常に非効率的だと思います。月単位の抽出件数が多く、それだけtable random accessの回数も増えるからです。
上のSQLで1ヶ月に該当する件数が14,248件であることが確認できます。
Indexを活用する場合、Index Read Blockを除いてもTable Random Accessの回数は最大14,248 Block(Clustering factorが非常に悪い場合)を読むことができます。
そうすると、月次照会である場合には、Index ScanよりTable Full Scanが性能上有利になります。
先ほど、Table Full Scan時の総Block Read量は4,441ということで比較が可能です。
したがって、日照会の場合はIndex Scanで、月照会の場合は既存のAccess方式であるTable Full Scanで実行されるようにSQLを分岐することが効率的でしょう。(※この部分については、Step9で詳しく説明します。)
月次照会が行われる場合、Table Full Scanが効率的であると判断されたからといってすぐに適用するよりも、Segment Sizeのトピックで言及したように、Table Full Scanを実行する場合にSegment Sizeの増加量を確認する手順が必要となります。
現時点では、Table Full Scanを実行することが有利ですが、該当SegmentのSizeの増加傾向が徐々に増加したり、特定の業務状況に応じて指数関数的に増加する可能性がある場合、近い将来にパフォーマンス問題を引き起こす主な原因として作用する可能性があるからです。
■ それでは、テーブルのSegmentのSizeの増加傾向を見てみましょう。
SegmentのSizeの増加傾向を確認した結果、テーブルSizeがどんどん増加することが確認できます。
このような場合、今は性能問題が発生しないかもしれませんが、時間が経つほどテーブルSizeが増加するため、性能問題が発生する可能性があります。
Note. Index Clustering Factorとは? テーブルにデータがロードされている順序がIndexのソートされた順序とどれだけ一致するかを示す数値です。Indexとテーブルのソート順序が非常に良い場合、Index Clustering FactorはBlock数とほぼ一致し、最悪の場合はテーブルの全体件数とほぼ一致します。Index Clustering Factorが良いほど、範囲が広いIndex Range Scanをする場合、Index Lookupを通じて発生するI/O量が大きく減少します。
3.4.6 STEP6.てーぶるじょうほうかくにん月単位での照会が行われるので、Time_IDカラムでPartition Tableへの移行を検討する必要があります。
Partition Tableに転換すれば、全体Segmentを読む必要がなく、該当するPartition Table(Bin3.4.6 STEP6.てーぶるじょうほうかくにんすることができるからです。
その前にSALES_INテーブルがパーティションテーブルかどうかを確認する手順が先行されなければならないでしょう。テーブルのパーティションかどうか確認してみましょう。
確認の結果、パーティションは構成されていないことがわかりました。
3.4.7 STEP7.アクセスパターン情報の確認
STEP6.段階を通じてSALES_INテーブルはNon-Partition Tableとして確認されました。
Partition Tableへの転換を検討する際、そのテーブルを照会するSQL照会パターン、つまりAccess Patternを確認しなければなりません。
Partition移行時、Partition Keyとして使用するカラムTime_idがSALES_INテーブルを照会するすべての業務で条件として使用されるかどうかが非常に重要となってくるからです。
これが重要となる理由としては、Time_id条件がない場合において、生成された全てのPartition TableをAccessしなければならない非効率が発生するからです。
SALES_IN テーブルの Access Pattern 照会結果
Access Patternを確認した結果、SALES_INテーブルの照会パターンは3つのタイプに分類されたことがわかります。
1つ目はPROD_ID、TIME_ID、2つ目はCUST_ID、TIME_ID、3つ目はTIME_IDで照会するパターンです。
SALES_INテーブルを照会するすべてのSQLはTIME_IDで照会されることを確認しました。
したがって、TIME_IDカラムでパーティションキーに選定することが大きな無理がないことをAccess Patternを通じて結論付けることができます。また、既存のIndexにTIME_IDカラムを追加して結合Indexで構成することも検討することができます。
SALES_IN_01とSALES_IN_02 IndexにTIME_IDを追加して結合Indexを生成すれば、Indexの効率が上がるからです。
3.4.8 STEP8.Partition構成とIndex作成
Access Patternを確認後、更にはIndexとパーティション構成についても確認した上で、該当するテーブルをパーティションテーブルに変更してから、以下のようなIndexを生成します。
3.4.9 STEP9.SQLブランチによる該当SQLパフォーマンスの確認
パーティションの構成とIndexの生成が完了したら、前述したように日別、月別照会パターン別にSQLを分岐して照会するようにしなければなりません。日照会パターンである場合には、Indexを活用し、月照会パターンである場合には、その区間のパーティションだけを全体照会するようにSQLを作成するようにしましょう。
なぜ上記のSQLを分岐するのか?についての核心は、to_dateからfrom_dateのBind変数を引いて抽出された結果が28より小さい場合は日照会、28より大きいか同じ場合は月照会に分けることができます。
SQL実行時、一番最初に実行される部分がBind変数を計算することなるため、日照会の場合には、日照会に該当するSQLのみを実行し、月照会を抽出する部分はFalseなので、該当するSQLを実行せずに終了します。
もちろん、逆の場合も同様です。
このように照会パターン別にSQLを分岐して照会すれば、日、月照会ともに最適な実行計画でデータを照会することができます。
今回のテーマを通じて、単純なSQLの性能改善をする過程においても、こんなにも多くの考慮事項と知っておくべき情報が多いことをここでは理解してほしいと思います。
短文SQLのチューニングは、単純にテーブル情報、Index情報など最小限の情報だけでも性能改善が可能ですが、深く考えれば考えるほど、もっと把握しなければならない情報が存在します。
結局、完璧な性能改善のためにはそれだけの時間と労力が必要となってくるのです。
この章の締めくくりとして、以下の文章を掲載しておきます。
「SQLの意味を把握し、それによって様々な情報を収集して性能改善作業に取り組む人の結果と、単に実行計画だけを見て独自のノウハウで性能改善作業をする人の結果は、どちらが信頼できるでしょうか?」
→ この答えはこれまでのブログを読んでいただければ、自ずとお分かりいただけると思います。
さて、4回シリーズでお送りしてまいりました「SQL チューニングのための情報分析方法」は今回で終了です。
次回からは新シリーズとして「SQL TuningとHINTの関係」をテーマに8回シリーズでお送りしていきます。
次回のSQLチューニングブログは
新シリーズ
「 SQL TuningとHINTの関係 」(1/8)について