手を入れたいがどうしても入れることができないSQLが多くある。ヒント文を入れるだけで、コストがうんと下がるのであれば、この方法は有効だ。
Oracle 8iよりStored Outlineという、あまり知られていない、しかし非常に有用な機能が追加された。
Stored Outlineとは、Ask Tomのある一稿から引用すると、「the ability for a developer to save an outline, a set of “hints to the server” for executing a specific SQL statement in the database」、とあり、日本語にすれば、「あるSQLを実行する際Oracleが参考とするヒント文、すなわちOutlineを保存する機能」となる。
この機能を使うことで、SQLが実行される際のヒント(というかむしろ実行計画そのもの)を、テーブルに保存することができる。また、USE_STORED_OUTLINESフラグを立てることで、その後は同SQLはそのテーブルに保存されたヒントを基に問い合わせが実行される。
ヒントが保存されるテーブルは、下記の2つのテーブル:
OUTLN.OL$
OUTLN.OL$HINTS
Outlineは、CREATE OUTLINE文で作成できる。作成されるOutlineに名前をつけたい場合は、CREATE OUTLINE Outline名 ON〜とすればよい。省略した場合は、Oracleが勝手に名前をつける。
CREATE OUTLINE ON
SELECT CODE_COMBINATION_ID
FROM GL.GL_CODE_COMBINATIONS
WHERE SEGMENT1 = :SEGMENT1;
これで、GL_CODE_COMBINATIONSをSEGMENT1で検索するSQLのOutlineが作成された。ただ、これだけでは今作成したOutlineは使用されない。Outlineの使用には、USE_STORED_OUTLINESフラグを立てる必要がある。フラグはSYSTEMかSESSIONで立てる。
ALTER SESSION SET USE_STORED_OUTLINES = TRUE;
これで今後同セッションでは、先ほど作成されたOutlineが、先ほどと同じSQLを実行する場合において、必ず使用されることになる。気をつけなければならないことは、実行するSQLは、CREATE OUTLINEを行ったSQLと一言一句異なってはならない。なぜなら、SQL Hash ValueをキーにOL$を検索するからである。スペース1つ違うだけで違うHash値となってしまい、Outlineは使用されなくなってしまう。
他にも、作成するOutlineをグループ分けし、使用の際グループ単位で使用/未使用を決定する方法などあるがここでは割愛する。次回にでも。
さて、ここまでは普通の解説。これからはOracleがコメントしていない部分を多少Hackする。ここから先はAt Your Own Riskで。何があっても私は責任をもてません。笑。
作成されたOutlineは、OL$とOL$HINTSに保存される。OL$は親情報で、SQL文やHash Valueなどを持つ。OL$HINTSはOL$の子供で、SQLのヒント文を持つ。このOL$HINTSの中身こそが、実行計画そのものである。
さて、Oracle 9iには、Diagnostic Packの中にOutline Editorという、Outlineの中身を変更するGUIツールがあるのだが、8iにはない、、、。こうなれば、直接OL$HINTSを変更するまでである。
とはいうものの、HINT_TEXTの項目に関するOracleのReferenceはない。そこで、別のSQLにてOutlineを作成し、それをオリジナルの物と置き換えるという荒業にて対応することにする。
1) 実行計画を変更したいSQL(ターゲットSQL)のOutlineを作る
先ほど述べたように、SQLは一言一句異なってはならない。Outlineを作成したいSQLをPL/SQL等からコピーし、SQL*PlusにペーストしCREATE OUTLINEする方法は通常あまりうまくいかない。このような場合有効な方法は、CREATE_STORED_OUTLINESフラグの使用である。このフラグを立てた場合、そのセッション(またはシステム)で実行されたSQL全てのOutlineが作成される。このフラグを立て、PL/SQL等の、「ターゲットSQL」が含まれているアプリケーションを実行する。
CREATE_STORED_OUTLINESのフラグの立て方:
ALTER SESSION SET CREATE_STORED_OUTLINES = TRUE;
これで、ターゲットとなるSQLのOutlineが作成されたはずである。(ごみOutlineも沢山できたはず)
2) ターゲットSQLのヒント文埋め込みバージョンを作る
次に、ターゲットSQLにヒント文を埋め込み、好みの実行計画になるよう調整する。この段階で、ヒント文をいくら埋め込んだ所で好みの実行計画にならない場合、その実行計画にはどこか無理がある。Outlineはあくまでもヒントであり、Oracleは実行時に使用判断を行い使用しないという選択もとり得る。
3) ヒント文埋め込みバージョンのOutlineを作る
ステップ2で作成したSQLのOutlineを、CREATE OUTLINEで作成する。名前は適当に。つけなくてもよい。
4) OL$HINTSの交換
1)で作成したOutlineのOL$HINTSの内容を、3)で作成したものと交換する。1)と3)のOL$HINTSの行数が異なる場合は、OL$のHINTCOUNTの更新も忘れずに。更新に臨んで、USE_STORED_OUTLINESフラグが立っていれば、落とすのが懸命だろう。
交換の方法は、SYSユーザーで下記SQLを実行する。1)で作成したOutlineの名前を「ORIGINALOL」、3)で作成したOutlineの名前を「MODIFIEDOL」とする。
UPDATE OL$HINTS
SET OL_NAME = DECODE(OL_NAME, 'ORIGINALOL', 'MODIFIEDOL',
'MODIFIEDOL', 'ORIGINALOL')
WHERE OL_NAME IN ('ORIGINALOL', 'MODIFIEDOL');
5) USE_STORED_OUTLINESのフラグを立てる
あとは、変更したOutlineが使用されるように、USE_STORED_OUTLINESのフラグを立てるのみ。同一セッションでターゲットSQLが実行されるならALTER SESSIONでいいが、ほとんどの場合は他のセッションのはずなので、ALTER SYSTEMでフラグを立てる。
ALTER SYSTEM SET USE_STORED_OUTLINES = TRUE;
以上である。あとは、1)でできたゴミOutlinesをDROPすればなお良い。
ちなみに、Outlineが実際に使われているかを確認する方法は、V$SQLを見ればよい。OUTLINE_CATEGORYという項目の値が、OL$のCATEGORYの値と一致すれば、Outlineは正しく使用されている。ブランクであれば、使用されていない。
以上の方法で、手を出したくても出せないSQL(Oracle Applicationやら)の実行計画を、SQLそのものは全く変更することなく、修正することが可能となる。
一番良いのは、そのような修正の必要があるSQLを生み出さないことである、、、。
追記:
同様の記事を探したところ、DBAzine.comに見つけた。
その記事で知ったのだが、Metalinkに同様のNoteがポストされている(Oracleはコメントしていないと思っていた)。92202.1である。(ただ、このNoteでは、OL$のHINTCOUNTの更新について漏れている。)
しかしやはりサポート外であることには変わりはない。