統計情報の取得と実行計画の固定について

さて、今回は多くの人が悩まされる統計情報と実行計画について、
お話していきたいと思います。


なお、基本的に対象のVerは10.2です。
なので、10.1でも通じるものはあると思いますが、
9.2以前の方は他を当たってください。
11.1以降の方もこの絡みだと、SPM(SQL Plan Management)とか新機能があるので、
他(インサイトテクノロジーとか)のサイトを当たってみてください。


では本編へ。長いぞー。


10gR1から、オプティマイザ統計の自動収集という機能が
新たに追加されてまして、デフォルトで動作する状態になっています。
まず、ここから説明しておきたいと思います。


10gから、RBOがサポートされなったため、ユーザはCBOを使用しなければなりません。
CBOは統計情報を元に実行計画を作成するため、ユーザが何も意識しなくても、
統計情報が取られた状態で、CBOが使えるようにしておくために、
10gでこの機能が追加されたと私は認識しています。
# Oracleさん的には違う意図があるのかもしれませんが。

自動オプティマイザ統計収集について

文字通り、自動でオプティマイザ統計を収集してくれる機能です。
Oracleのスケジューラによって、定期的に実行されます。
動作の要件としては、statistics_levelパラメータが TYPICAL 以上になっていること
というものがありますが、これはデフォルトでこうなので、
特に何もしなければ、動いているということになります。

いつ実行されるのか

スケジューラジョブに設定されているウィンドウで確認することができます。
スケジューラジョブは DBA_SCHEDULER_JOBS で確認します。

SQL> col JOB_NAME for a30
SQL> col SCHEDULE_NAME for a40
SQL> select JOB_NAME ,SCHEDULE_NAME from DBA_SCHEDULER_JOBS;

JOB_NAME                       SCHEDULE_NAME
------------------------------ ----------------------------------------
AUTO_SPACE_ADVISOR_JOB         MAINTENANCE_WINDOW_GROUP
GATHER_STATS_JOB               MAINTENANCE_WINDOW_GROUP
FGR$AUTOPURGE_JOB
PURGE_LOG                      DAILY_PURGE_SCHEDULE


「GATHER_STATS_JOB」というのが、自動オプティマイザ統計収集のジョブにです。
この結果の「SCHEDULE_NAME」をさらに、
DBA_SCHEDULER_WINGROUP_MEMBERS にあてると、
ウィンドウ名が引っ張ってこれます。

SQL> col WINDOW_GROUP_NAME for a30
SQL> col WINDOW_NAME for a20
SQL> select WINDOW_GROUP_NAME ,WINDOW_NAME from DBA_SCHEDULER_WINGROUP_MEMBERS;

WINDOW_GROUP_NAME              WINDOW_NAME
------------------------------ --------------------
MAINTENANCE_WINDOW_GROUP       WEEKNIGHT_WINDOW
MAINTENANCE_WINDOW_GROUP       WEEKEND_WINDOW


この結果の「WINDOW_NAME」をさらに
DBA_SCHEDULER_WINDOWS にあてることで、
実際にいつ動く設定になっているかが確認できます。

SQL> col WINDOW_NAME for a20
SQL> col REPEAT_INTERVAL for a80
SQL> col DURATION for a30
SQL> select WINDOW_NAME ,REPEAT_INTERVAL ,DURATION from DBA_SCHEDULER_WINDOWS;

WINDOW_NAME          REPEAT_INTERVAL                                                                  DURATION
-------------------- -------------------------------------------------------------------------------- ------------------------------
WEEKNIGHT_WINDOW     freq=daily;byday=MON,TUE,WED,THU,FRI;byhour=22;byminute=0; bysecond=0            +000 08:00:00
WEEKEND_WINDOW       freq=daily;byday=SAT;byhour=0;byminute=0;bysecond=0                              +002 00:00:00


上記はデフォルトの設定です。
REPEAT_INTERVAL の中には、実行開始タイミングが書いてあります。
# freqが頻度
# by[day|hour|minute|second]が開始[曜日|時|分|秒]
DURATION の中には、実行時間が書いてあります。
よって下記のようになります。

  • WEEKNIGHT_WINDOW

月曜〜金曜まで、毎日22時から、8時間実行する。

  • WEEKEND_WINDOW

土曜の0時から2日間(48時間)実行する。


ちなみに、土曜の0時は金曜日分がまだ動いてますので、
WEEKEND_WINDOW は実際には、土曜の6時から42時間実行となるようです。

実行タイミングを変更したい!

デフォルトの設定だと、バッチ処理のタイミングにぶち当たる!
というシステムは多いのではないでしょうか?
あと、平日と土日でなんで分かれてるねん!という方も。
当然、ウインドウの設定を変更することで、対応が可能です。
停止もできますが、これは後ろで書きますので、最後まで読んでください。


マニュアルでは、下記のあたりに記述があります。


Oracle Database 管理者ガイド 10gリリース2(10.2)
B19224-02
23 メンテナンス・ウィンドウを使用した自動システム・タスクの管理
メンテナンス・ウィンドウ
http://otndnld.oracle.co.jp/document/products/oracle10g/102/doc_cd/server.102/B19224-02/tasks.htm#862483


ちょこっと一例を。


例)
デフォルトの設定から、毎日(日〜土) 20時〜24時 の実行に変更する。


この場合、下記のような手順で作業を行ないます。


1.「MAINTENANCE_WINDOW_GROUP」から「WEEKEND_WINDOW」を削除する。
※ただし、ほかのスケジューラジョブで上記ウインドウグループが
使用されていないことは確認してくださいね。
ちなみに上にも出ていますが、デフォルトでは、「AUTO_SPACE_ADVISOR_JOB」が
同じメンテナンスウインドウで動いています。


DBMS_SCHEDULER.REMOVE_WINDOW_GROUP_MEMBER を実行することで、
ウインドウグループからメンバーを削除できます。

SQL> exec DBMS_SCHEDULER.REMOVE_WINDOW_GROUP_MEMBER('MAINTENANCE_WINDOW_GROUP','WEEKEND_WINDOW');

PL/SQLプロシージャが正常に完了しました。

SQL> select WINDOW_GROUP_NAME ,WINDOW_NAME from DBA_SCHEDULER_WINGROUP_MEMBERS;

WINDOW_GROUP_NAME              WINDOW_NAME
------------------------------ --------------------
MAINTENANCE_WINDOW_GROUP       WEEKNIGHT_WINDOW


「DBA_SCHEDULER_WINGROUP_MEMBERS」から「WEEKEND_WINDOW」が削除されていますね。


ちなみに、マニュアルのこのあたりに載っています。

Oracle Database 管理者ガイド 10gリリース2(10.2)
B19224-02
27 スケジューラの使用
ウィンドウ・グループの使用
http://otndnld.oracle.co.jp/document/products/oracle10g/102/doc_cd/server.102/B19224-02/scheduse.htm#298966


2.「WEEKNIGHT_WINDOW」の設定を変更する。
まず、マニュアルを参考に、「repeat_interval」を変更します。


DBMS_SCHEDULER.SET_ATTRIBUTE を実行することで、
repeat_interval の変更が可能です。

SQL> exec DBMS_SCHEDULER.SET_ATTRIBUTE('WEEKNIGHT_WINDOW','repeat_interval','freq=daily;byday=MON,TUE,WED,THU,FRI,SAT,SUN;byhour=0;byminute=0;bysecond=0');

PL/SQLプロシージャが正常に完了しました。

SQL> select WINDOW_NAME ,REPEAT_INTERVAL ,DURATION from DBA_SCHEDULER_WINDOWS where WINDOW_NAME = 'WEEKNIGHT_WINDOW';

WINDOW_NAME          REPEAT_INTERVAL                                                                  DURATION
-------------------- -------------------------------------------------------------------------------- ------------------------------
WEEKNIGHT_WINDOW     freq=daily;byday=MON,TUE,WED,THU,FRI,SAT,SUN;byhour=0;byminute=0;bysecond=0      +000 08:00:00


続いて「duration」も変更しないといけないのですが、
これは、管理者ガイドに記述がないので、下記のマニュアルを参考にします。


Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス 10g リリース2(10.2)
B19245-02
93 DBMS_SCHEDULER
http://otndnld.oracle.co.jp/document/products/oracle10g/102/doc_cd/appdev.102/B19245-02/d_sched.htm#CIHHBGGI


こんな感じですね。簡単。

SQL> exec DBMS_SCHEDULER.SET_ATTRIBUTE('WEEKNIGHT_WINDOW','duration','+000 04:00:00');

PL/SQLプロシージャが正常に完了しました。

SQL> select WINDOW_NAME ,REPEAT_INTERVAL ,DURATION from DBA_SCHEDULER_WINDOWS where WINDOW_NAME = 'WEEKNIGHT_WINDOW';

WINDOW_NAME          REPEAT_INTERVAL                                                                  DURATION
-------------------- -------------------------------------------------------------------------------- ------------------------------
WEEKNIGHT_WINDOW     freq=daily;byday=MON,TUE,WED,THU,FRI,SAT,SUN;byhour=0;byminute=0;bysecond=0      +000 04:00:00


これで、設定が変更できました。
その他、リソースプランを設定するとか、
いろいろと細かい設定もできるようですが、
ここでは触れません。マニュアルみてください。

統計収集の対象は?

収集対象となるのは、以下のようなオブジェクトです。

  • 統計情報が収集されていないオブジェクト
  • 最後の統計情報収集からデータの10%以上が変化したオブジェクト


注意しないといけないのは、
このオブジェクトのリストアップはウインドウの開始直後に行なわれるということです。
ですので、デフォルトの設定の場合、
平日(月〜金)であれば、22時過ぎごろの段階で上記の条件に
当てはまるオブジェクトがその日の統計情報収集の対象となります。

どのように統計情報を取得するべきかの検討

あて、では実際にどのようなタイミングで、
どのような頻度で統計情報を取得するべきか、についてですが、
これは正直言ってシステムによります。
下記のような情報を参考にシステム単位、もっと細かくするなら、
オブジェクト単位で最適なタイミングを検討してください。

実行計画が再解析されるとき

CBOというのは、以下のような局面で、実行されたSQLの実行計画を再解析します。

  • SQLが共有プールからエイジアウトしたとき
  • SQL内で呼ばれているオブジェクトの統計情報が新しくなったとき
  • SQL内で呼ばれているオブジェクトの構造が変わったとき

など。


よって、統計情報を収集することは、実行計画の再解析をまねきます。
従来と異なる実行計画でSQLが実行される可能性が高くなると考えたほうがよいです。

CBOの精度

統計情報を サンプル 100% で取得すれば、かならず最適な実行計画をCBOが生成する。


というような勘違いをされている方がよくいますが、
これは全く違います。


「絶対に確実な実行計画を出せる統計情報の取り方は存在しない。」


というのが本当のところです。
特に複雑なクエリーになりますと、実行計画のパターンというのは膨大になります。
CBOに与えられた解析のための時間は限られています。
その膨大な中から必ず最適なものを見つけるには短すぎるのです。
また、統計情報はサンプル100%で取得しても、ヒストグラムを取得しても、
表のある一面を切り取った情報に過ぎません。
これらの情報だけで判断するCBOが、時にとんでもないパフォーマンスの
実行計画を提示するのは、それは仕方の無いことです。

システムの特性を考慮した対処方法

上記より、対象のシステムがどのようなシステムかによって、
統計情報の取り方のスタンスは変わってきます。

クリティカルなシステムでなく、多少性能劣化しても問題ないシステム

こんなシステムがいまだ存在するのかどうかわかりませんが、
社内の情報系のシステムはこれになるんでしょうか。
こういったシステムは自動統計情報収集をONにして全然問題ないでしょう。
たまーに、極端にパフォーマンスの悪いSQLが出てくるかもわかりませんが、
そういったものは、対処療法的に、ヒント句などで対応しましょう。

クリティカルなシステムで、多少の性能劣化が大問題になるシステム

最速より安定を求めるようなシステムの場合ですね。
外向きのシステムはほとんどこうだと思いますが。
こういったシステムの場合、実行計画が変化しないようにするべきです。
よって、頻繁な統計情報を収集は避けるべきです。
全てがクリティカルではないけども、
特定のSQLのみに限定して性能を安定させたいようなケースでは、
そのSQLが実行される場合は必ず同じ統計情報で実行させるような仕組みが必要になりますね。

実行計画の変化を制御する方法あれこれ。

思いつく限りでこんなとこでしょうか。

  • 自動統計情報収集をやめて、統計情報を取らなくする。
  • SQLが絞れるなら、対象のオブジェクトだけ、統計情報をロックする。
  • 常に同じ統計情報が使用できるように、SQL実行前に統計情報のimportを行なう。
  • HINT句でガチガチに固める。
  • プランスタビリティを使って、実行計画をアウトラインに保存する。


あと、変化しちゃったときの対処法として


では、一個ずつ詳しくみていきましょう。

自動統計情報収集をやめて、統計情報を取らなくする。

スケジューラジョブで動いているので、これを停止すればいいわけです。

SQL> exec DBMS_SCHEDULER.DISABLE('GATHER_STATS_JOB');

PL/SQLプロシージャが正常に完了しました。

SQL> select JOB_NAME ,ENABLED from DBA_SCHEDULER_JOBS;

JOB_NAME                       ENABLED
------------------------------ ---------------
AUTO_SPACE_ADVISOR_JOB         TRUE
GATHER_STATS_JOB               FALSE
FGR$AUTOPURGE_JOB              FALSE
PURGE_LOG                      TRUE


ただし、この場合、データの急激な変化にCBOがついていけない可能性がでてきます。
また、drop->create を繰り返すテーブルなんかがあると、
dropした段階でそのオブジェクトに紐づいた統計情報も削除されますので、
以降統計情報がない状態で稼動することとなります。
さらに、10gからはINDEXを作成すると、作成時に無条件でINDEXに対してのみ、
統計情報の収集がされます。(関連するテーブルの統計情報まで取られるわけではありません。)
よって、drop->create を行なうINDEXなんかにも注意が必要です。

SQLが絞れるなら、対象のオブジェクトだけ、統計情報をロックする。

自動統計情報の収集は続けるけども、こいつだけは統計情報を固めておきたい!
という場合に有効です。
下記で可能になります。

SQL> exec dbms_stats.lock_table_stats('SCOTT','TEST');

PL/SQLプロシージャが正常に完了しました。

SQL> select TABLE_NAME ,STATTYPE_LOCKED from USER_TAB_STATISTICS;

TABLE_NAME STATTYPE_LOCKED
---------- ---------------
DEPT
EMP
BONUS
SALGRADE
TEST       ALL
TEST_2


この状態で統計情報を収集すると下記のエラーが発生します。
もちろん、自動統計情報収集で、下記エラーが発生したからといって、
収集自体が止まってしまうことはありません。

SQL> exec dbms_stats.gather_table_stats('SCOTT','TEST');
BEGIN dbms_stats.gather_table_stats('SCOTT','TEST'); END;

*
行1でエラーが発生しました。:
ORA-20005: object statistics are locked (stattype = ALL)
ORA-06512: "SYS.DBMS_STATS", 行13182
ORA-06512: "SYS.DBMS_STATS", 行13202
ORA-06512: 行1


ロックを解除するには、下記を実行します。

SQL> exec dbms_stats.unlock_table_stats('SCOTT','TEST');

PL/SQLプロシージャが正常に完了しました。

SQL> select TABLE_NAME ,STATTYPE_LOCKED from USER_TAB_STATISTICS;

TABLE_NAME STATTYPE_LOCKED
---------- ---------------
DEPT
EMP
BONUS
SALGRADE
TEST
TEST_2

6行が選択されました。


なお、この場合でも、テーブルやINDEX自体の削除を行なうと、
属性ごと消えてしまうので、注意してください。

常に同じ統計情報が使用できるように、SQL実行前に統計情報のimportを行なう。

統計格納表というものを作って、そこに統計情報を保管しておき、
SQL実行前にそこから戻して、常に同じ統計情報でSQLを実行しようという方法です。


1.ひとまず、今の統計情報を確認します。

SQL> select table_name ,last_analyzed from user_tables where table_name = 'TEST';

TABLE_NAME LAST_ANA
---------- --------
TEST       08-06-03


6/3に取得されたようです。
ちなみにこの作業をしているのは、

SQL> !date
2008年  6月 18日 水曜日 06:08:22 JST


です。サーバの時計が狂っているので、
別に超早起きなわけではありません。
まだそこまで年は取ってない。


2.まず、統計格納表をつくります。

SQL> exec dbms_stats.create_stat_table('SCOTT','STAT_TABLE');

PL/SQLプロシージャが正常に完了しました。


3.続いて、統計情報を統計格納表にいれます。

SQL> exec dbms_stats.export_table_stats(ownname => 'SCOTT' ,tabname => 'TEST' ,stattab => 'STAT_TABLE');

PL/SQLプロシージャが正常に完了しました。


4.テスト的に統計情報を消してみます。

SQL> exec dbms_stats.delete_table_stats('SCOTT','TEST');

PL/SQLプロシージャが正常に完了しました。

SQL> select table_name ,last_analyzed from user_tables where table_name = 'TEST';

TABLE_NAME LAST_ANA
---------- --------
TEST


5.統計情報を統計格納表から戻します。

SQL> exec dbms_stats.import_table_stats(ownname => 'SCOTT' ,tabname => 'TEST' ,stattab => 'STAT_TABLE');

PL/SQLプロシージャが正常に完了しました。

SQL> select table_name ,last_analyzed from user_tables where table_name = 'TEST';

TABLE_NAME LAST_ANA
---------- --------
TEST       08-06-03


これで戻りました。


対象のSQLで関連するテーブルに対して、全て上記の作業を行なえば、
必ず、同じ実行計画でSQLが実行できるというわけです。
また、統計情報のimportにはほとんど時間がかかりませんし、
drop->create をしているようなテーブルにも対応できますので、
かなりおすすめな方法です。


ちなみに、このあたりのプロシージャの使用方法や
ほかの引数などは下記のマニュアルで確認してください。


Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンス 10g リリース2(10.2)
B19245-02
103 DBMS_STATS
http://otndnld.oracle.co.jp/document/products/oracle10g/102/doc_cd/appdev.102/B19245-02/d_stats.htm#CIHBIEII

HINT句でガチガチに固める。

これは言わずもがなですね。
ガッチガッチのビッタンビッタンに固めてください。

プランスタビリティを使って、実行計画をアウトラインに保存する。

実行計画をアウトラインに格納して、そのSQLが実行されたときは、
必ずアウトラインにある実行計画を使用する。というやりかたです。


1.まずは権限を付与します。
CREATE ANY OUTLINE システム権限が必要です。

SQL> grant CREATE ANY OUTLINE to scott;

権限付与が成功しました。


2.とりあえず現状の実行計画を確認します。
emp と dept でテスト用SQLの実行計画を確認します。
前にPKを消したので、ハッシュ結合(HASH JOIN)になっています。

SQL> explain plan for
  2  select count(*) from emp e ,dept d where e.deptno = d.deptno;

解析されました。

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 1546158010

----------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |     1 |    26 |     7  (15)| 00:00:01 |
|   1 |  SORT AGGREGATE     |      |     1 |    26 |            |          |
|*  2 |   HASH JOIN         |      |    12 |   312 |     7  (15)| 00:00:01 |
|   3 |    TABLE ACCESS FULL| DEPT |     4 |    52 |     3   (0)| 00:00:01 |
|   4 |    TABLE ACCESS FULL| EMP  |    12 |   156 |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("E"."DEPTNO"="D"."DEPTNO")

Note
-----
   - dynamic sampling used for this statement

20行が選択されました。


3.アウトラインにこのSQLの実行計画を保存します。
実際に実行して保存する場合は、ALTER SESSION ... 文を使います。

SQL> ALTER SESSION SET CREATE_STORED_OUTLINES = 'OL_TEST';

セッションが変更されました。

SQL> select count(*) from emp e ,dept d where e.deptno = d.deptno;

  COUNT(*)
----------
        12

SQL> ALTER SESSION SET CREATE_STORED_OUTLINES = FALSE;

セッションが変更されました。


アウトラインだけを保管する場合は、CREATE OUTLINE 文を実行します。

SQL> CREATE OUTLINE OUTLINE_TEST_01 for category OL_TEST_1 on
  2  select count(*) from emp e ,dept d where e.deptno = d.deptno;

アウトラインが作成されました。


4.テスト的に実行計画を変化させるために、deptにindexを作ります。
これで、HASH JOIN ではなく、NESTED LOOP で実行されることになるはずです。

SQL> create unique index dept_ind on dept ( deptno ) ;

索引が作成されました。

SQL> explain plan for
  2  select count(*) from emp e ,dept d where e.deptno = d.deptno;

解析されました。

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 3708632848

--------------------------------------------------------------------------------
| Id  | Operation           | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |          |     1 |    26 |     3   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE     |          |     1 |    26 |            |          |
|   2 |   NESTED LOOPS      |          |    12 |   312 |     3   (0)| 00:00:01 |
|   3 |    TABLE ACCESS FULL| EMP      |    12 |   156 |     3   (0)| 00:00:01 |
|*  4 |    INDEX UNIQUE SCAN| DEPT_IND |     1 |    13 |     0   (0)| 00:00:01 |
--------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   4 - access("E"."DEPTNO"="D"."DEPTNO")

Note
-----
   - dynamic sampling used for this statement

20行が選択されました。


なりましたね。


5.アウトラインを使って、昔の実行計画で実行されるようにしてみる。
まず、alter session ... 文 でアウトラインを使うように、
設定変更します。このパラメータには、作成時に指定したカテゴリ名を
指定します。

SQL> alter session set use_stored_outlines = OL_TEST;

セッションが変更されました。


で、実行してみます。
AUTOTRACE を ON にして、実行と・・・。

SQL> set autot on
SQL> select count(*) from emp e ,dept d where e.deptno = d.deptno;

  COUNT(*)
----------
        12


実行計画
----------------------------------------------------------
Plan hash value: 1546158010

----------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |     1 |    26 |     7  (15)| 00:00:01 |
|   1 |  SORT AGGREGATE     |      |     1 |    26 |            |          |
|*  2 |   HASH JOIN         |      |   409 | 10634 |     7  (15)| 00:00:01 |
|   3 |    TABLE ACCESS FULL| DEPT |   409 |  5317 |     3   (0)| 00:00:01 |
|   4 |    TABLE ACCESS FULL| EMP  |   409 |  5317 |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("E"."DEPTNO"="D"."DEPTNO")

Note
-----
   - outline "SYS_OUTLINE_08061806512960501" used for this statement


統計
----------------------------------------------------------
        547  recursive calls
        207  db block gets
        114  consistent gets
          0  physical reads
        576  redo size
        434  bytes sent via SQL*Net to client
        400  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
          1  rows processed


うん。無事 HASH JOIN のほうを使っていますね。
こんな塩梅で対象のSQLをバシバシアウトラインに格納していけばOKです。


注意点としては、
統計情報が反映されないので、
経年変化による、緩やかな性能劣化が起こりやすいです。
このあたりをウォッチする仕組みをつくっておくことが
必須だと思います。(OEMとかでね。)

オプティマイザ統計をバックアップから戻す

これはちょっと毛色が違ってしまうのですが、
10gではデフォルトで31日前までに取られた統計情報をバックアップしてくれてますので、
パフォーマンス劣化が発生したら、ここから戻して、対応することも可能です。


バックアップの履歴に関しては、USER_TAB_STATS_HISTORY で確認ができます。

SQL> select TABLE_NAME ,STATS_UPDATE_TIME from USER_TAB_STATS_HISTORY where table_name = 'TEST';

TABLE_NAME STATS_UPDATE_TIME
---------- ---------------------------------------------------------------------------
TEST       08-06-03 11:28:09.105833 +09:00
TEST       08-06-18 05:59:17.157908 +09:00
TEST       08-06-18 06:02:40.576784 +09:00
TEST       08-06-18 06:06:45.985675 +09:00
TEST       08-06-18 06:07:19.919457 +09:00

SQL> exec dbms_stats.gather_table_stats('SCOTT','TEST');

PL/SQLプロシージャが正常に完了しました。

SQL> select TABLE_NAME ,STATS_UPDATE_TIME from USER_TAB_STATS_HISTORY where table_name = 'TEST';

TABLE_NAME STATS_UPDATE_TIME
---------- ---------------------------------------------------------------------------
TEST       08-06-03 11:28:09.105833 +09:00
TEST       08-06-18 05:59:17.157908 +09:00
TEST       08-06-18 06:02:40.576784 +09:00
TEST       08-06-18 06:06:45.985675 +09:00
TEST       08-06-18 06:07:19.919457 +09:00
TEST       08-06-18 07:02:28.222101 +09:00

6行が選択されました。


では、この統計情報を 6/3 12:00 の時点のものに戻してみましょう。
6/3 12:00 の時点では、「08-06-03 11:28:09.105833」に取得された
統計情報が入っているので、「LAST_ANALYZED」に、この日時が出てくれば、
戻し成功ってわけですね。

SQL> select table_name ,last_analyzed from user_tables where table_name = 'TEST';

TABLE_NAME LAST_ANALYZED
---------- -------------------
TEST       2008/06/18 07:02:28

SQL> exec dbms_stats.restore_table_stats(ownname => 'SCOTT' ,tabname => 'TEST' ,as_of_timestamp => '08-06-03 12:00:00.000000');

PL/SQLプロシージャが正常に完了しました。

SQL> select table_name ,last_analyzed from user_tables where table_name = 'TEST';

TABLE_NAME LAST_ANALYZED
---------- -------------------
TEST       2008/06/03 11:28:09


戻し成功!
これも戻す時間は一瞬です。
新たに統計情報を取り直すより、よっぽど速い。
まあ、障害発生時には、まず、元の状態に戻す。というのが鉄則なので、
パフォーマンス劣化が発生したら、まずこれをやるのがいいかもしれません。


ということで、統計情報と実行計画について、長々とまとめてみました。
お役に立てば幸いです。
また、何かあれば、メールでも、コメントでも、ブクマでもお待ちしております。


では。