月別アーカイブ: 2015年3月

12c新機能「Identity Column」の検証①

今週の名言

「自分がわずかなことしか知らないということを知るためには、多くのことを知る必要がある。」
モンテーニュ

Oracle 12c新機能について

今週からOracle 12cの新機能を検証します。

実はOracle 12c 発表されたと思ったらリリース1(R1)はもうターミナル・リリースになるようです。(Database In-Memoryなど大幅に機能拡張:Oracle Database 12cR1の最新パッチセット12.1.0.2がリリースされました

最近のOracle RDBMSは、R1のうちはイノベーター、アーリーアダプターまでが採用する傾向にあって、R2以降本格的にアーリーマジョリティ他に普及していくような都市伝説(?)があるように思います。

私が関わっている案件もほとんどが11gR2で、本番環境で12cに移行するというものをあまり聞いたことはありません。エンドユーザも状況を見ているのではないかという印象を持っています。

従って、12c新機能ネタはまだ新鮮味がある気がしますので、筆者が興味を持った機能を今後積極的に紹介していきたいと思います。

「Identity Column」とは?

12c新機能というと、プラガブル・データベースやDatabase In-Memoryなどが注目されていますが、このブログではどちらかというとあまり陽の当たらない機能で面白そうなものを紹介していこうと思います。

それも、単に機能を紹介するのではなく、実際に使う立場で検証してみたいと思います。

さて「Identity Column」ですが、ANSI準拠のIDENTITY Columnを実装したもので、平たく言うとInsert文における自動生成キー取り出し機能のことです。

マニュアルは以下を参照してください。

Oracle® Database新機能ガイド
12cリリース1 (12.1)
B71327-03
2.1.6.3 IDENTITY列

この機能は上記マニュアルの記述に先行した

  • 2.1.6.1 Oracleの順序に基づく列のデフォルト値
  • 2.1.6.2 明示的なNULL挿入での列のDEFAULT値

の組み合わせで使用する機能であるとも言えます。

「百聞は一見に如かず」とりあえず実際のDDL文から確認していきましょう。

基本動作の確認

検証環境

検証環境はOracle Linux 6.5とOracle12R1です。

SQL> !uname -r
3.8.13-16.2.1.el6uek.x86_64

SQL> select BANNER from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
PL/SQL Release 12.1.0.2.0 - Production
CORE	12.1.0.2.0	Production
TNS for Linux: Version 12.1.0.2.0 - Production
NLSRTL Version 12.1.0.2.0 - Production

CREATE TABLE文

検証用のTESTユーザはすでに作成済みです。CREATE SEQUENCE権限が付与されていることが必要です。

SQL> show user
ユーザーは"TEST"です。

SQL> create table t1
2 (c1 number GENERATED BY DEFAULT ON NULL AS IDENTITY
3 ,c2 varchar2(10)
4 );

表が作成されました。

GENERATED 〜 AS IDENTITY」がIDENTITY句と呼ばれる記述ですが、「BY DEFAULT ON NULL」はオプションです。これを省略するとデフォルトの「ALWAYS」となります。(先で機能を説明するため、あえてこの設定にします。)

シーケンス確認

IDENTITY句を指定すると、裏で自動的にシーケンス・オブジェクト(順序)が作られます。
USER_SEQUENCEからその状況を確認しておきましょう。

SQL> select SEQUENCE_NAME,INCREMENT_BY,CACHE_SIZE,LAST_NUMBER
  2  ,CYCLE_FLAG,ORDER_FLAG
  3  from user_sequences;

SEQUENCE_NAME             INCREMENT_BY CACHE_SIZE LAST_NUMBER C O
------------------------- ------------ ---------- ----------- - -
ISEQ$$_91795                         1         20           1 N N

ISEQ$$_91795というシステムが命名した順序が作成されているのがわかります。
キャッシュ・サイズはデフォルトの「20」です。

1行インサートする。

それでは、まずは1行インサートしてみましょう。C1列には何も指定せずC2列のみ値を設定してインサートします。
さらに、直後の順序の状況を確認します。

SQL> insert into t1(c2) values ('abc');

1行が作成されました。

SQL> select SEQUENCE_NAME,INCREMENT_BY,CACHE_SIZE,LAST_NUMBER
  2  from user_sequences;

SEQUENCE_NAME             INCREMENT_BY CACHE_SIZE LAST_NUMBER
------------------------- ------------ ---------- -----------
ISEQ$$_91795                         1         20          21

LAST_NUMBERが 1+20(CACHE_SIZE)となっています。つまり最初の生成で1〜20までの数字がキャッシュされ、このインサートでは「1」が払い出されます。(次の生成では21〜40までがキャッシュされる予定です。)

もう1行インサートする。

さらにもう1行インサートします。

SQL> insert into t1(c2) values ('xyz');

1行が作成されました。

SQL> select c1,c2 from t1;

        C1 C2
---------- ----------
         1 abc
         2 xyz

SQL> select SEQUENCE_NAME,INCREMENT_BY,CACHE_SIZE,LAST_NUMBER
  2  from user_sequences;

SEQUENCE_NAME             INCREMENT_BY CACHE_SIZE LAST_NUMBER
------------------------- ------------ ---------- -----------
ISEQ$$_91795                         1         20          21

2行作成されていることがわかります。キャッシュされている番号から払い出されているため順序の内容は変化していません。

一度ロールバックし、再度2行インサートする。

2行インサートした状態でコミットせずにロールバックすると、0行となります。
ここで、先ほど行った2行のインサートを行いC1列にどのような値が入るか確認します。
さらに今度はコミットします。

SQL> rollback;

ロールバックが完了しました。

SQL> select SEQUENCE_NAME,INCREMENT_BY,CACHE_SIZE,LAST_NUMBER
  2  from user_sequences;

SEQUENCE_NAME             INCREMENT_BY CACHE_SIZE LAST_NUMBER
------------------------- ------------ ---------- -----------
ISEQ$$_91795                         1         20          21

SQL> insert into t1(c2) values ('abc');

1行が作成されました。

SQL> insert into t1(c2) values ('xyz');

1行が作成されました。

SQL> select c1,c2 from t1;

        C1 C2
---------- ----------
         3 abc
         4 xyz

SQL> commit;

コミットが完了しました。

テーブルはロールバックされましたが、順序「ISEQ$$_91795」には何の影響も与えません。
キャッシュされている次の番号「3」と「4」が続けて払い出されているのがわかります。

テーブルをドロップして順序の状況を確認する。

SQL> drop table t1;

表が削除されました。

SQL> select SEQUENCE_NAME,INCREMENT_BY,CACHE_SIZE,LAST_NUMBER
  2  from user_sequences;

SEQUENCE_NAME             INCREMENT_BY CACHE_SIZE LAST_NUMBER
------------------------- ------------ ---------- -----------
ISEQ$$_91795                         1         20          21

SQL> select ISEQ$$_91795.nextval from dual;

   NEXTVAL
----------
         5

テーブルを削除しても、順序はそのまま残っていることがわかります。
しかも、キャッシュされているシーケンス番号から次の番号が何の問題もなく払い出せることが確認できます。

リサイクルビンのパージ

テーブルを削除してもリサイクルビンで管理されるだけなので、念のためリサイクルビンを空にして順序の状況を確認します。

SQL> purge recyclebin;

リサイクルビンがパージされました。

SQL> select SEQUENCE_NAME,INCREMENT_BY,CACHE_SIZE,LAST_NUMBER
  2  from user_sequences;

レコードが選択されませんでした。

リサイクルビンを空にして、つまり本当にテーブルを削除すると、テーブル作成時に自動的に作成された順序も削除されるようです。

「BY DEFAULT ON NULL」の意味を確認する。

「IDENTITY Column」の特徴は、値を指定しないで(つまりNullで)インサートしても自動的に連番が設定されることができるというものですが、明示的に値を設定することもできるいうのが「BY DEFAULT ON NULL」オプションです。

結論から言うと、どちらでもよいというのは意味がなくむしろ別の問題の原因となるのでこのオプションを指定すべきではないと考えます。
デフォルトの「ALWAYS」を指定すべきでしょう。

以下に、その理由を説明する検証を行っていきます。

キャッシュサイズを最小にする。

順序のキャッシュサイズをデフォルトの20にしておくと、次に払い出されるシーケンス番号が見えにくくなるので、キャッシュサイズを最小にしてみます。(パフォーマンス上の理由からキャッシュサイズはデフォルトの20より小さい値にすべきではありませんが、検証上あえて小さくします。)

SQL> create table t2
  2  (c1 number       GENERATED BY DEFAULT ON NULL AS IDENTITY (CACHE 1)
  3  ,c2 varchar2(10)
  4  );
create table t2
*
行1でエラーが発生しました。:
ORA-04010: CACHEには1より大きい値を指定する必要があります

おっと!「CACHE 1」という設定は許されていないようです。

それでは「NOCACHE」でやり直します。

SQL> create table t2
  2  (c1 number       GENERATED BY DEFAULT ON NULL AS IDENTITY (NOCACHE)
  3  ,c2 varchar2(10)
  4  );

表が作成されました。

SQL> select SEQUENCE_NAME,INCREMENT_BY,CACHE_SIZE,LAST_NUMBER
  2  from user_sequences;

SEQUENCE_NAME             INCREMENT_BY CACHE_SIZE LAST_NUMBER
------------------------- ------------ ---------- -----------
ISEQ$$_91798                         1          0           1

今度はうまくいきました。

C1列に主キーを設定する。

C1列は本来であればID列であるので主キーを設定します。

SQL> alter table t2 add constraint pk_t2 primary key (c1);

表が変更されました。

C1列に値を指定せずにインサートする

C1列は順序が設定されている列なので、値を指定せずにインサートするのが通常のインサートです。
とりあえず最初のレコードをインサートします。

SQL> insert into t2(c2) values ('abc');

1行が作成されました。

SQL> select SEQUENCE_NAME,INCREMENT_BY,CACHE_SIZE,LAST_NUMBER
  2  from user_sequences;

SEQUENCE_NAME             INCREMENT_BY CACHE_SIZE LAST_NUMBER
------------------------- ------------ ---------- -----------
ISEQ$$_91798                         1          0           2

SQL> select c1,c2 from t2;

        C1 C2
---------- ----------
         1 abc

C1列に値を指定してインサートする。

今度は、明示的にC1列に値を指定してインサートしてみます。

SQL> insert into t2(c1,c2) values (2,'xyz');

1行が作成されました。

SQL> select SEQUENCE_NAME,INCREMENT_BY,CACHE_SIZE,LAST_NUMBER
  2  from user_sequences;

SEQUENCE_NAME             INCREMENT_BY CACHE_SIZE LAST_NUMBER
------------------------- ------------ ---------- -----------
ISEQ$$_91798                         1          0           2

特に問題なくインサートできます。
(ALWAYSの場合はここでエラーとなります。)
また、LAST_NUMBERが「2」のままであることも注目すべきです。つまり次に払い出されるシーケンス番号は「2」です。

今度はC1列に値を指定しないでインサートする。

SQL> insert into t2(c2) values ('xyz');
insert into t2(c2) values ('xyz')
*
行1でエラーが発生しました。:
ORA-00001: 一意制約(TEST.PK_T2)に反しています

SQL> select c1,c2 from t2;

        C1 C2
---------- ----------
         1 abc
         2 xyz

主キーの重複エラーが発生します。
なぜなら、既に「C1=2」となるレコードをインサートしているので、順序が払い出した「2」が重複してしてしまいエラーとなりました。

もう一度C1列に値を指定しないでインサートする。

ORA-00001エラーが発生するとそのレコードはインサートされませんが、もう一度値を指定しないでインサートするレコードはどうなるでしょうか?

SQL> insert into t2(c2) values ('def');

1行が作成されました。

SQL> select c1,c2 from t2;

        C1 C2
---------- ----------
         1 abc
         2 xyz
         3 def

SQL> select SEQUENCE_NAME,INCREMENT_BY,CACHE_SIZE,LAST_NUMBER
  2  from user_sequences;

SEQUENCE_NAME             INCREMENT_BY CACHE_SIZE LAST_NUMBER
------------------------- ------------ ---------- -----------
ISEQ$$_91798                         1          0           4

今度は、次に払い出されるシーケンス番号が「3」となり重複しないので、エラーは発生しません。

結論:IDENTITY列は「GENERATED ALWAYS AS IDENTITY」で設定すべき。

「Identity Column」のメリットあるいは注意点に関する考察は次回以降に回したいと思いますが、今回確認した中での結論はID列に値を指定する選択を許す「BY DEFAULT ON NULL」オプションは使うべきではないということです。
言い換えると「BY DEFAULT ON NULL」とは、値を指定しなければ(Null)順序から番号を払い出し、指定すればその値でインサートするという仕様と考えることもできます。
値を指定するとエラーとなるデフォルトの「ALWAYS」を使うべきだと思います。

create table t1
(c1 number       GENERATED ALWAYS AS IDENTITY  -- (ALWAYS)は省略可
,c2 varchar2(10)
);

続く

「オプティマイザ統計の保留」の検証(その8)

今週の名言

「人は日本の歴史に50ページ書いてもらうより、世界の歴史に1ページ書いてもらうことを心掛けねばならぬ。」
後藤新平

シリーズまとめ

「オプティマイザ統計の保留」について検証してきた今回のシリーズも序章を含めると10回目になりました。

この機能は他にもいろいろ検証して確認したいことはあるのですが、他のテーマも追いかけたいので区切りがよいところでまとめておきたいと思います。

新しい機能にどう向き合うか

私はITというものに対して、ウンチクを語る対象ではなく道具としてビジネスに貢献するものでなければならないという強い思いがあります。

従ってある新機能を目にした時には「これは何の役にたつのだろうか?」とか「あの問題に対してよい解決策になるのではないか?」という視点で確認してみたいという気持ちになります。

Oracle10g以降ルールベース・オプティマイザ(RBO)がサポートされなくなりましたが、クルマがマニュアル・シフトからオートマチック・シフトになったのと同じくらいのインパクトがあったのではないでしょうか。

パラメータ・チューニングやヒント句を駆使して性能改善をバリバリ行ってきたエンジニアにとって、Oracle10gは大きな変換点だったのではないかと思います。
先日もある飲み会で「Oracle9iは結構面白いバージョンだった。」というような意見を一人のベテランエンジニアから聞きました。

Oracle10g以降自動化が進んで、DBAが手を動かして問題解決するという機会は確かに減りましたが、自動化を過信したいわゆる「自動化の落とし穴」という問題も深刻になってきました。

このシリーズは「自動化の落とし穴」にまつわる混乱を「オプティマイザ統計の保留」の検証(序章②)で紹介するところから始めました。

コストベース・オプティマイザ(CBO)というそれまでとは違った仕組みに対して、実行計画が意図せず急変してしまったと言う理由で、統計情報を取ることに消極的であったりヒント句で実行計画をガチガチに固めてしまっていたりする例を未だによく見かけます。

もうすぐOracle12c R2がリリースされるという現在となっては「オプティマイザ統計の保留」はすでに新機能とは呼べなくなっているかもしれませんが、CBOの仕組みをよく理解し、この機能を活用することによって「失敗のない運用」を行うことは十分可能であると思います。

「オプティマイザ統計の保留」の特徴

それではシリーズを振り返ってこの機能の特徴を整理していきます。

1. 保留統計情報は実行計画に影響を与えない

実行計画は公開されたディクショナリ統計情報によって算出されます。従ってプライベート・エリアに格納されディクショナリ統計を更新しない保留統計情報は実行計画に何ら影響を与えることがないというのが一番の特徴です。

一度算出された実行計画を変動させる要因がないということは、実行計画は事実上固定されます。

2. 保留統計情報は履歴を持たない

保留統計は履歴を持ちません。これは以下のような2つの意味を持ちます。

  • 最新の統計情報のみが保持される
  • プライベート・エリア(SYSAUX表領域)を圧迫しない

定期的に統計情報を取得しても、古い統計情報の履歴を削除する必要はありません。

3. セッション単位で保留統計情報を使った実行計画が確認できる

OPTIMIZER_USE_PENDING_STATISTICS初期化パラメータをセッション単位で一時的に「TRUE」に設定し、SQLを実行すると保留統計情報を使用した実行計画が作成されます。

これをExplain PlanやSQL*PlusのAutotrace機能で確認することで、公開統計情報を使用した実行計画と比較することができます。

4. 保留統計情報は手動で公開することができる

もし、保留統計情報を使用した実行計画の方が優れていることを確認できれば、DBMS_STATS.PUBLISH_PENDING_STATSプロシージャを実行することにより任意のタイミングで保留統計情報を公開することができます。

これにより、より優れた実行計画を永続的に使用することができます。

5. 保留統計情報はStandard Editionでも使うことができる

今まで触れてこなかったのですが、この機能はStandard Editionでも使用することができます。

Enterprise EditionではSQL計画管理(SPM:SQL Plan Management)により、統計情報に左右されない安定した実行計画の選択を行うことができます。

SPMのように高度な実行計画の管理はできませんが、SEでも「オプティマイザ統計の保留」によって同じような運用を行うことができます。(手動で運用する分のコストはかかります。)

ベスト・プラクティス

それでは、「オプティマイザ統計の保留」を活用した運用のベスト・プラクティスを考えてみましょう。

もちろん、環境によって個別の要件があるので、柔軟に対応することは重要です。

1. 初期状態では統計情報は「公開」しておく

全く統計情報を取得しない状態で「保留」にすることは可能ですが、統計情報を取得してそれを使用した実行計画が作成されるまでは「公開」のままにしておいた方がよいかもしれません。

2. 実行計画に問題がないことを確認し、テーブル単位で公開属性を「保留」に変更する

公開属性をテーブル単位で「保留」に変更すれば、少なくともそのテーブルに関する実行計画は新たな統計情報によって不安定になることなく固定されます。

ただし、一つのテーブルは多くのSQL文で使用されていますので、あるテーブルの属性変更がどのSQLに関係しているのかを把握するためには、以前このブログで紹介した「V$SQL_PLANでCRUD表モドキを作ってみる②」を参考にして調査をしてもよいかもしれません。

3. 定期的に統計情報を取得して評価する

公開属性が「保留」になっていれば、どんなに統計情報を取得したとしても実行計画が変更されることはありません。従って統計情報を定期的に取得することは継続すべきであると考えます。

これは自動統計情報収集機能にまかせてもよいし、DBAが任意にスケジュールしてもよいかもしれません。

「公開」と「保留」統計情報を比較し、より正しい実行計画が得られることが明らかであれば、その保留統計を都度公開していけばよいのです。

4. 統計情報のロックをうまく使い分ける

揮発性の高いテーブル、つまり一日中に頻繁にTRANCATEが実行されたり、バルク・インサートの対象となっているような、すなわち定期的な統計情報の取得でSELECT時使用される正確な統計情報を収集することができないテーブルに対しては、保留統計情報を使用しても最適な結果が得られないことが考えられます。

そのような場合は、統計情報を削除した後にロックし統計情報がない状態で運用した方がよいかもしれません。

このようなテーブルに対しては、SQL文実行時に動的統計が自動的に収集されます。(ダイナミック・サンプリング)

統計情報の「保留」と「ロック」を必要に応じて使い分けることでより最適な運用を図ることができます。

今回でこのシリーズは終わりにしますが、まだ検証していないことも沢山ありますので、またこのテーマを再開させるかもしれません。

次回は

次回は12cのある新機能に注目して検証していきたいと思います。

日曜の夜に翌週の朝食を準備する

朝食はカルビーのフルーツグラノーラ

私はだいたい朝食にカルビーのフルーツグラノーラを食べています。

時々気分を変えてご飯やパンを食べたりもしますが、ここ15年以上はグラノーラに牛乳をかけたものとヨーグルトかチーズというのが定番の朝食です。

グラノーラの朝食

  • 栄養のバランスがいい(気がする)。
  • シンプルで飽きない。
  • すぐに用意できる。

というのがその理由ですが、仕事に出かける前の忙しい時でも気軽に食べられるのでとても気に入っています。

分量を計るのは面倒

確かに「すぐに用意できる」というメリットはあるのですが、牛乳200g・グラノーラ50gをその都度スケールで計るのはちょっと面倒です。

50gというのは微妙な量で、なかなか目分量で器に入れるのは難しいです。

そこで、忙しい朝のためにちょっと工夫をしてみることにしました。

50gずつ小分けにしておく

牛乳200gは計量カップで食べる直前すぐに計ることができるので、グラノーラを50gずつ小分けにしてジップロックの小さい方の袋に入れておきます。

50gずつ計る

コーヒー豆を計るスプーン1杯が約10gなので、5杯分を袋に入れて重さを確認し微調整します。

これを一週間分以上日曜日の夜に準備して、大き目のジップロックに入れておきます。

朝食を10秒で用意する

朝になったら一袋分のグラノーラを器にあけて、カップで計った牛乳を上からかけます。これだけだと10秒もかかりません。

この他にバナナをスプーンで切って入れたり、ヨーグルトやチーズを冷蔵庫から出す時間を合わせても1分以内に朝食の準備ができます。

工夫次第で忙しい朝も朝食を手早く準備できれば、余裕を持ってスタートできます。

お試しください!!

 

 

「オプティマイザ統計の保留」の検証(その7)

今週の名言

「最も強い者が生き残るのではなく、最も賢い者が生き延びるのでもない。唯一生き残るのは、変化できる者である。」
- チャールズ・ダーウィン -

前回のおさらい

  • 統計情報公開属性(PUBLISH属性)はテーブルあるいはスキーマ単位で変更することができる。(正確な仕様はマニュアル参照)
  • スキーマ単位で公開属性を「FALSE」に変更しても、その後に作成されるテーブルの属性はデフォルト「TRUE」のままなので、そのテーブル単位あるいはスキーマ単位で「FALSE」に再設定する必要がある。

統計情報の保留とロックの関係は?

実行計画を統計情報の取得によって変動させないための機能として「統計情報のロック」という機能があります。(Oracle10g以降で使用可能)
ロックが設定されたテーブルに対して統計情報を取得しようとするとORA-20005エラーが発生し、収集ができないようになっています。
これは、公開属性を保留に設定されたテーブルに対してはどのように作用するでしょうか?
ロックが設定されていたとしても、保留統計情報が取得できればよいと思うのですが実際はどうなのでしょうか?
早速確認してみましょう。

1. 検証準備

最初に検証用のテーブルとインデックスを作成し、初期状態を確認します。

SQL> create table test1(col1 number,col2 varchar2(20));

表が作成されました。

SQL> create index test1_idx on test1 (col1);

索引が作成されました。

SQL> create table test2(col1 number,col2 varchar2(20));

表が作成されました。

SQL> create index test2_idx on test2 (col1);

索引が作成されました。

SQL> @showstat

レコードが選択されませんでした。

STAT    OWNER  TABLE_NAME    NUM_ROWS     BLOCKS AVG_ROW_LEN SAMPLE_SIZE LAST_ANALYZED
------- ------ ----------- ---------- ---------- ----------- ----------- -------------------
CURRENT TEST   TEST1
CURRENT TEST   TEST2

テーブル(インデックス)は存在していますが、データは0件です。

2. 初期データ1,000件作成と統計情報取得

今までの検証と同様に1,000件の初期データをInsertし統計情報を取得します。

SQL> begin
  2   for i in 1..1000 loop
  3    insert into test1 values (i,'TEST DATA');
  4    commit;
  5   end loop;
  6  end;
  7  /

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

SQL> begin
  2   for i in 1..1000 loop
  3    insert into test2 values (i,'TEST DATA');
  4    commit;
  5   end loop;
  6  end;
  7  /

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

SQL> select count(*) from test1;

  COUNT(*)
----------
      1000

SQL> select count(*) from test2;

  COUNT(*)
----------
      1000

SQL> begin
  2   DBMS_STATS.GATHER_TABLE_STATS('TEST','TEST1');
  3  end;
  4  /

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

SQL> begin
  2   DBMS_STATS.GATHER_TABLE_STATS('TEST','TEST2');
  3  end;
  4  /

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

SQL> @showstat

STAT    OWNER  TABLE_NAME    NUM_ROWS     BLOCKS AVG_ROW_LEN SAMPLE_SIZE LAST_ANALYZED       SAVE_TIME
------- ------ ----------- ---------- ---------- ----------- ----------- ------------------- -------------------
PUBLISH TEST   TEST1                                                                         2015-03-xx 10:59:54
PUBLISH TEST   TEST2                                                                         2015-03-xx 11:00:08

STAT    OWNER  TABLE_NAME    NUM_ROWS     BLOCKS AVG_ROW_LEN SAMPLE_SIZE LAST_ANALYZED
------- ------ ----------- ---------- ---------- ----------- ----------- -------------------
CURRENT TEST   TEST1             1000          5          14        1000 2015-03-xx 10:59:54
CURRENT TEST   TEST2             1000          5          14        1000 2015-03-xx 11:00:08

3. TEST1表をロックする

それでは、一方のテーブルTEST1表をロックし、USER_TAB_STATISTICSビューからステータスを確認します。

SQL> EXECUTE DBMS_STATS.LOCK_TABLE_STATS ('TEST','TEST1');

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

SQL> select TABLE_NAME,STATTYPE_LOCKED from USER_TAB_STATISTICS;

TABLE_NAME  STATTYPE_L
----------- ----------
TEST1       ALL
TEST2

比較のためにTEST2表はそのままにしておきます。

4. データを1,000件追加し、再度統計情報を取得する。

両方のテーブルにデータを1,000件ずつ追加し、再度統計情報を取得します。
TEST1表はロックされているので、ORA-20005エラーが発生するはずです。

SQL> begin
  2   for i in 1001..2000 loop
  3    insert into test1 values (i,'TEST DATA');
  4    commit;
  5   end loop;
  6  end;
  7  /

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

SQL> begin
  2   for i in 1001..2000 loop
  3    insert into test2 values (i,'TEST DATA');
  4    commit;
  5   end loop;
  6  end;
  7  /

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

SQL> select count(*) from test1;

  COUNT(*)
----------
      2000

SQL> select count(*) from test2;

  COUNT(*)
----------
      2000

SQL> begin
  2   DBMS_STATS.GATHER_TABLE_STATS('TEST','TEST1');
  3  end;
  4  /
begin
*
行1でエラーが発生しました。:
ORA-20005: object statistics are locked (stattype = ALL)
ORA-06512: "SYS.DBMS_STATS", 行20337
ORA-06512: "SYS.DBMS_STATS", 行20360
ORA-06512: 行2

SQL> begin
  2   DBMS_STATS.GATHER_TABLE_STATS('TEST','TEST2');
  3  end;
  4  /

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

SQL> @showstat

STAT    OWNER  TABLE_NAME    NUM_ROWS     BLOCKS AVG_ROW_LEN SAMPLE_SIZE LAST_ANALYZED       SAVE_TIME
------- ------ ----------- ---------- ---------- ----------- ----------- ------------------- -------------------
PUBLISH TEST   TEST1                                                                         2015-03-xx 10:59:54
PUBLISH TEST   TEST2                                                                         2015-03-xx 11:00:08
PUBLISH TEST   TEST1             1000          5          14        1000 2015-03-xx 10:59:54 2015-03-xx 11:01:21
PUBLISH TEST   TEST2             1000          5          14        1000 2015-03-xx 11:00:08 2015-03-xx 11:05:46

STAT    OWNER  TABLE_NAME    NUM_ROWS     BLOCKS AVG_ROW_LEN SAMPLE_SIZE LAST_ANALYZED
------- ------ ----------- ---------- ---------- ----------- ----------- -------------------
CURRENT TEST   TEST1             1000          5          14        1000 2015-03-xx 10:59:54
CURRENT TEST   TEST2             2000         13          14        2000 2015-03-xx 11:05:46

予想通りTEST1表でORA-20005エラーが発生しました。(40行目)
TEST2表は1,000件の統計情報が履歴として残り(59行目)、2,000件の統計情報が新たに公開されています。(64行目)

5. スキーマ単位で公開属性を「FALSE」に設定する

SQL> begin
  2   DBMS_STATS.SET_SCHEMA_PREFS('TEST','PUBLISH','FALSE');
  3  end;
  4  /

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

SQL> select TABLE_NAME,DBMS_STATS.GET_PREFS('PUBLISH','TEST',TABLE_NAME) PUBLISH
  2  from USER_TABLES;

TABLE_NAME  PUBLISH
----------- ----------
TEST1       FALSE
TEST2       FALSE

6. データをさらに1,000件追加し、再度統計情報を取得する。

両方のテーブルの公開属性を「保留=FALSE」に設定しました。
ロック中のTEST1表において保留統計情報は取得できるのでしょうか?

SQL> begin
  2   for i in 2001..3000 loop
  3    insert into test1 values (i,'TEST DATA');
  4    commit;
  5   end loop;
  6  end;
  7  /

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

SQL> begin
  2   for i in 2001..3000 loop
  3    insert into test2 values (i,'TEST DATA');
  4    commit;
  5   end loop;
  6  end;
  7  /

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

SQL> select count(*) from test1;

  COUNT(*)
----------
      3000

SQL> select count(*) from test2;

  COUNT(*)
----------
      3000

SQL> begin
  2   DBMS_STATS.GATHER_TABLE_STATS('TEST','TEST1');
  3  end;
  4  /
begin
*
行1でエラーが発生しました。:
ORA-20005: object statistics are locked (stattype = ALL)
ORA-06512: "SYS.DBMS_STATS", 行20337
ORA-06512: "SYS.DBMS_STATS", 行20360
ORA-06512: 行2

SQL> begin
  2   DBMS_STATS.GATHER_TABLE_STATS('TEST','TEST2');
  3  end;
  4  /

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

SQL> @showstat

STAT    OWNER  TABLE_NAME    NUM_ROWS     BLOCKS AVG_ROW_LEN SAMPLE_SIZE LAST_ANALYZED       SAVE_TIME
------- ------ ----------- ---------- ---------- ----------- ----------- ------------------- -------------------
PUBLISH TEST   TEST1                                                                         2015-03-xx 10:59:54
PUBLISH TEST   TEST2                                                                         2015-03-xx 11:00:08
PUBLISH TEST   TEST1             1000          5          14        1000 2015-03-xx 10:59:54 2015-03-xx 11:01:21
PUBLISH TEST   TEST2             1000          5          14        1000 2015-03-xx 11:00:08 2015-03-xx 11:05:46
PENDING TEST   TEST2             3000         13          14        3000 2015-03-xx 11:11:07 3000-12-01 01:00:00

STAT    OWNER  TABLE_NAME    NUM_ROWS     BLOCKS AVG_ROW_LEN SAMPLE_SIZE LAST_ANALYZED
------- ------ ----------- ---------- ---------- ----------- ----------- -------------------
CURRENT TEST   TEST1             1000          5          14        1000 2015-03-xx 10:59:54
CURRENT TEST   TEST2             2000         13          14        2000 2015-03-xx 11:05:46

やはり、TEST1表でORA-20005エラーが発生しました。(40行目)
また、TEST2表では保留統計情報が取得されていますが、TEST1表では取得されていません。(60行目)

つまり、統計情報がロックされていると、保留統計情報も取得されないことがわかります。

ちなみに、スキーマ単位で統計情報を取得する

begin
 DBMS_STATS.GATHER_SCHEMA_STATS('TEST');
end;
/

を使用すると、ロックされたテーブルがあっても、内部的に取得動作がスキップされるようでORA-20005エラーは発生しません。(今回は取得できないことを確認するためにテーブル単位で取得しました。)

まとめ

統計情報をロックしたテーブルでも、保留統計情報を取得できるか確認しましたが、ロックはやはりロックでした。
今回の検証で、ロックされたテーブルにおいては保留統計情報であっても取得されないことを確認することができました。

今回はここまで

「オプティマイザ統計の保留」の検証(その6)

今週の名言

 「過去から学び、今日のために生き、未来に対して希望を持つ。大切なことは、何も疑問を持たない状態に陥らないことである。」
- アルベルト・アインシュタイン -

前回のおさらい

前回は、保留統計情報が履歴を持つかということを確認し、最後に収集された統計情報のみが保持されることを確認しました。
これは公開統計情報が、自動化メンテナンスタスクによって削除されるまで履歴が残り続けるのと比べて、領域を圧迫する危険性がより少ないことを意味します。
保留統計情報の隠れたメリットとも言えるかもしれません。

確認項目2. スキーマ単位、テーブル単位で公開属性を設定する場合の動きは?

今までの検証ではテーブル単位で公開属性を変更していましたが、スキーマ単位でも公開属性を変更することができます。
実際の運用を考えるとスキーマに含まれるテーブルは数百を超えることもあるので、スキーマ単位で一括して属性を変更できた方が効率的です。
今回は、スキーマ単位で公開属性を変更する時の動きを確認します。

1. 検証準備

前回までの検証で使用したTEST表を削除し、同じ構造のTEST1表、TEST2表を作成しそれぞれインデックスを作成します。
作成後に(デフォルトの)公開属性を確認します。

SQL> drop table test purge;

Table dropped.

SQL> select tname from tab;

no rows selected

SQL> create table test1(col1 number,col2 varchar2(20));

Table created.

SQL> create index test1_idx on test1 (col1);

Index created.

SQL> create table test2(col1 number,col2 varchar2(20));

Table created.

SQL> create index test2_idx on test2 (col1);

Index created.

SQL> select TABLE_NAME,DBMS_STATS.GET_PREFS('PUBLISH','TEST',TABLE_NAME) PUBLISH
  2  from USER_TABLES;

TABLE_NAME  PUBLISH
----------- -----------
TEST1       TRUE
TEST2       TRUE

デフォルトの公開属性は「TRUE=公開」となっています。

2. TEST1表の公開属性をFALSEに変更する。

それでは、今までと同じようにTEST1表のみ「TRUE→FALSE」に変更してみます。

SQL> begin
  2   DBMS_STATS.SET_TABLE_PREFS('TEST','TEST1','PUBLISH','FALSE');
  3  end;
  4  /

PL/SQL procedure successfully completed.

SQL> select TABLE_NAME,DBMS_STATS.GET_PREFS('PUBLISH','TEST',TABLE_NAME) PUBLISH
  2  from USER_TABLES;

TABLE_NAME  PUBLISH
----------- -----------
TEST1       FALSE
TEST2       TRUE

変更していないTEST2表はTRUEのままです。

3. スキーマ単位で公開属性をFALSEに変更する。

今度はTESTスキーマに対して公開属性を「FALSE」に設定変更します。

SQL> begin
  2   DBMS_STATS.SET_SCHEMA_PREFS('TEST','PUBLISH','FALSE');
  3  end;
  4  /

PL/SQL procedure successfully completed.

SQL> select TABLE_NAME,DBMS_STATS.GET_PREFS('PUBLISH','TEST',TABLE_NAME) PUBLISH
  2  from USER_TABLES;

TABLE_NAME  PUBLISH
----------- -----------
TEST1       FALSE
TEST2       FALSE

TEST2表も「FALSE」となっていることがわかります。

4. テーブル単位で公開属性をTRUEに変更する。

次に、「FALSE→TRUE」も見てみます。まずTEST2表のみ「TRUE」に変更します。

SQL> begin
  2   DBMS_STATS.SET_TABLE_PREFS('TEST','TEST2','PUBLISH','TRUE');
  3  end;
  4  /

PL/SQL procedure successfully completed.

SQL> select TABLE_NAME,DBMS_STATS.GET_PREFS('PUBLISH','TEST',TABLE_NAME) PUBLISH
  2  from USER_TABLES;

TABLE_NAME  PUBLISH
----------- -----------
TEST1       FALSE
TEST2       TRUE

5. スキーマ単位で公開属性をTRUEに変更する。

SQL> begin
  2   DBMS_STATS.SET_SCHEMA_PREFS('TEST','PUBLISH','TRUE');
  3  end;
  4  /

PL/SQL procedure successfully completed.

SQL> select TABLE_NAME,DBMS_STATS.GET_PREFS('PUBLISH','TEST',TABLE_NAME) PUBLISH
  2  from USER_TABLES;

TABLE_NAME  PUBLISH
----------- -----------
TEST1       TRUE
TEST2       TRUE

SQL> begin
  2   DBMS_STATS.SET_SCHEMA_PREFS('TEST','PUBLISH','FALSE');
  3  end;
  4  /

PL/SQL procedure successfully completed.

スキーマ単位でも「FALSE→TRUE」の変更が可能であることがわかります。

6. スキーマ単位で公開属性をFALSEにしておいてから、新規にテーブルを作成する。

スキーマ単位で公開属性を保留にしておけば、次に新規作成するテーブルに公開属性は反映されるのでしょうか?
早速やってみましょう。

SQL> select TABLE_NAME,DBMS_STATS.GET_PREFS('PUBLISH','TEST',TABLE_NAME) PUBLISH
  2  from USER_TABLES;

TABLE_NAME  PUBLISH
----------- -----------
TEST1       FALSE
TEST2       FALSE

SQL> create table test3(col1 number,col2 varchar2(20));

Table created.

SQL> create index test3_idx on test3 (col1);

Index created.

SQL> select TABLE_NAME,DBMS_STATS.GET_PREFS('PUBLISH','TEST',TABLE_NAME) PUBLISH
  2  from USER_TABLES;

TABLE_NAME  PUBLISH
----------- -----------
TEST1       FALSE
TEST2       FALSE
TEST3       TRUE

新規作成したテーブルの公開属性は、何もしなければ「TRUEに設定されていることがわかります。

7. 公開属性が「TRUE」であるテーブルの有無を確認し、スキーマ単位で「FALSE」にするには。

SQL> select TABLE_NAME,DBMS_STATS.GET_PREFS('PUBLISH','TEST',TABLE_NAME) PUBLISH
  2  from USER_TABLES
  3  where DBMS_STATS.GET_PREFS('PUBLISH','TEST',TABLE_NAME) = 'TRUE';

TABLE_NAME  PUBLISH
----------- -----------
TEST3       TRUE

SQL> begin
  2   DBMS_STATS.SET_SCHEMA_PREFS('TEST','PUBLISH','FALSE');
  3  end;
  4  /

PL/SQL procedure successfully completed.

SQL> select TABLE_NAME,DBMS_STATS.GET_PREFS('PUBLISH','TEST',TABLE_NAME) PUBLISH
  2  from USER_TABLES;

TABLE_NAME  PUBLISH
----------- -----------
TEST1       FALSE
TEST2       FALSE
TEST3       FALSE

まとめ

  • テーブル単位でもスキーマ単位でも公開属性を変更することができる。
  • テーブル作成時の公開属性は「TRUE」なので、スキーマ全体で公開属性を「FALSE 」にするような運用をしているような場合、テーブル単位あるいはスキーマ単位で「FALSE」に再設定する必要がある。

今日はここまで。

続く

「オプティマイザ統計の保留」の検証(その5)

今週の名言

「学べば学ぶほど、自分が何も知らなかった事に気づく、気づけば気づくほどまた学びたくなる。」
- アルベルト・アインシュタイン -

前回のおさらい

前回は、取得された統計情報が統計情報公開属性の違いによって、どこにどのように格納されるのかということを、実際に確かめてみました。
ちょっと複雑になってきたので、イメージでおさらいをしておきます。

統計情報の保留

  1. デフォルト状態では、DBMS_STATSプロシージャによって取得された統計情報は、プライベート・エリアに履歴として格納されます。(上図①前半)
  2. 1.と同時に、取得された最新の統計情報はディクショナリにも格納されます。(上図①後半、赤枠のデータ)
  3. ディクショナリに格納された統計情報を基にコスト・ベース・オプティマイザ(CBO)は実行計画を算出します。言い換えるとSQLが実行される時にディクショナリの統計情報が変更されていれば、新たな実行計画が算出されます。(上図②)
  4. 統計情報公開属性がFALSEの場合、取得された統計情報は保留統計情報としてプライベート・エリアに格納されますが、ディクショナリには反映されません。従って実行計画に影響を与えることもありません。(上図③)
  5. 保留中の統計情報は(公開)日時が「3000/12/01 01:00:00」のデータとして扱われます。(青字行)
  6. DBMS_STATS.PUBLISH_PENDING_STATSプロシージャによって、保留統計情報は公開されディクショナリに反映されます。(上図④)
  7. セッション単位でOPTIMIZER_USE_PENDING_STATISTICS初期化パラメータをTRUEに変更すると、CBOは保留統計情報を使用した実行計画を一時的に算出します。(上図⑤)これにより、管理者は保留統計情報を使用した実行計画の方がより優れているかどうかを判断することができます。

疑問点をまとめてみる

「オプティマイザ統計の保留」の基本的な仕様(統計情報の管理と内部動作)はご理解いただけたかと思いますが、もう少し細かい部分を確認していきます。

1. 保留統計情報は履歴を持つか?

公開統計情報は、収集の都度履歴として保存されます。今回の検証では触れませんが保存された履歴はずっとプライベート・エリアに残り続けるのではなく、自動化メンテナンス・タスクの中で削除ルールに従って古いものから自動的に削除されるようになっています。

それでは、統計情報が保留されている状態で新たに統計情報を取得すると、その統計情報は公開属性がFALSEであれば新たな保留統計情報として保存されるはずですが、今まで保持されていた保留統計情報は履歴として残るのでしょうか?それとも保留統計情報は最新のものが1つだけ保持されるのでしょうか?あるいは別の保存のされ方があるのでしょうか?

その辺の動きを確かめてみたいと思います。

2. スキーマ単位、テーブル単位で公開属性を設定する場合の動きは?

「オプティマイザ統計の保留」の検証(その1)で紹介しましたが、公開属性は、スキーマまたはテーブル単位で変更することができます。
スキーマ単位で属性を変更した後に、個々のテーブル単位で設定を変更することは可能なのでしょうか?
あるいは、個々にテーブル単位で設定した状態で、スキーマ単位で設定変更すればすべてのテーブルに反映されるのでしょうか?

これも実際に確かめてみたいと思います。

3. 統計情報をロックした状態で「オプティマイザ統計の保留」はどのように機能するのだろうか?

テーブル単位で統計情報をロックすると、テーブルの統計情報は固定されます。
統計情報が固定されると実行計画は変更されないので、実行計画が変更されては困る場合に「統計情報のロック」はよく使われるのですが、この機能は「オプティマイザ統計の保留」に影響を与えるのでしょうか?

実際に確かめてみます。

4. 公開統計と保留統計の比較について

「オプティマイザ統計の保留」の検証(その1)でコマンドを、(その2)では結果を紹介しましたが、公開統計と保留統計を比較するレポートを再度確認します。
特に運用の場面で活用することを意識して使い方を考えてみたいと思います。

検証開始

0. 確認スクリプトをちょっと修正

実は前回まで使用していた確認スクリプトは、この後使うにはちょっと足りないので修正を加えます。

  • テーブル名が「TEST」で始まるものを対象とする。(27,46行目)
  • SAVE_TIMEの昇順でソート表示する。(28行目)
SQL> get showstat.sql
  1  SET LINESIZE      150
  2  COLUMN OWNER      FORMAT a6
  3  COLUMN TABLE_NAME FORMAT a11
  4  select
  5    case when h.savtime >  systimestamp
  6          then 'PENDING'
  7         when h.savtime <= systimestamp
  8          then 'PUBLISH'
  9    end as        STAT
 10  , u.name        OWNER
 11  , o.name        TABLE_NAME
 12  , h.rowcnt      NUM_ROWS
 13  , h.blkcnt      BLOCKS
 14  , h.avgrln      AVG_ROW_LEN
 15  , h.samplesize  SAMPLE_SIZE
 16  , to_char(h.analyzetime,'yyyy-mm-dd hh24:mi:ss') LAST_ANALYZED
 17  , to_char(h.savtime,'yyyy-mm-dd hh24:mi:ss')     SAVE_TIME
 18  From
 19    sys.user$                     u
 20  , sys.obj$                      o
 21  , sys.wri$_optstat_tab_history  h
 22  where h.obj#    = o.obj#
 23  and   o.type#   = 2
 24  and   o.owner#  = u.user#
 25  and   u.name    = 'TEST'
 26  and   o.name    like 'TEST%'
 27  order by h.savtime
 28  ;
 29  select
 30    'CURRENT'     STAT
 31  , u.name        OWNER
 32  , o.name        TABLE_NAME
 33  , t.rowcnt      NUM_ROWS
 34  , t.blkcnt      BLOCKS
 35  , t.avgrln      AVG_ROW_LEN
 36  , t.samplesize  SAMPLE_SIZE
 37  , to_char(t.analyzetime,'yyyy-mm-dd hh24:mi:ss') LAST_ANALYZED
 38  from
 39    sys.user$ u
 40  , sys.tab$  t
 41  , sys.obj$  o
 42  where o.owner#  = u.user#
 43  and   o.obj#    = t.obj#
 44  and   u.name    = 'TEST'
 45* and   o.name    like 'TEST%'

SQL> @showstat

STAT    OWNER  TABLE_NAME    NUM_ROWS     BLOCKS AVG_ROW_LEN SAMPLE_SIZE LAST_ANALYZED       SAVE_TIME
------- ------ ----------- ---------- ---------- ----------- ----------- ------------------- -------------------
PUBLISH TEST   TEST                                                                          2015-02-xx 12:30:29
PUBLISH TEST   TEST              1000          5          14        1000 2015-02-xx 12:30:29 2015-02-xx 12:38:27
PUBLISH TEST   TEST             50000        244          15       50000 2015-02-xx 12:33:39 2015-02-xx 12:42:22
PUBLISH TEST   TEST              1000        244          14        1000 2015-02-xx 12:41:37 2015-02-xx 12:56:07

STAT    OWNER  TABLE_NAME    NUM_ROWS     BLOCKS AVG_ROW_LEN SAMPLE_SIZE LAST_ANALYZED
------- ------ ----------- ---------- ---------- ----------- ----------- -------------------
CURRENT TEST   TEST                 0          0           0           0 2015-02-xx 12:56:07

1. 保留統計情報は履歴を持つか?

上に挙げた4つの疑問点のうち、今回は最初の項目について確認したいと思います。
前回までの検証で使用したTEST表をまた使っていきます。

1. 検証用テーブルの再作成

まず最初にTESTテーブルを再作成するところから始めます。
今回はデータをInsertする前にインデックスまで作成します。

SQL> drop table test purge;

Table dropped.

SQL> create table test(col1 number,col2 varchar2(20));

Table created.

SQL> create index test_idx on test (col1);

Index created.

SQL> @showstat

no rows selected

STAT    OWNER  TABLE_NAME    NUM_ROWS     BLOCKS AVG_ROW_LEN SAMPLE_SIZE LAST_ANALYZED
------- ------ ----------- ---------- ---------- ----------- ----------- -------------------
CURRENT TEST   TEST

2. テーブル統計情報公開属性を「FALSE=保留」に変更

SQL> begin
  2   DBMS_STATS.SET_TABLE_PREFS('TEST','TEST','PUBLISH','FALSE');
  3  end;
  4  /

PL/SQL procedure successfully completed.

SQL> col prefs for a10
SQL> select DBMS_STATS.GET_PREFS('PUBLISH','TEST','TEST') prefs from dual;

PREFS
----------
FALSE

2. データ1,000件インサートおよび統計情報の取得(1回目)

SQL> begin
  2   for i in 1..1000 loop
  3    insert into test values (i,'TEST DATA');
  4    commit;
  5   end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

SQL> select count(*) from test;

  COUNT(*)
----------
      1000

SQL> begin
  2   DBMS_STATS.GATHER_TABLE_STATS('TEST','TEST');
  3  end;
  4  /

PL/SQL procedure successfully completed.

SQL> @showstat

STAT    OWNER  TABLE_NAME    NUM_ROWS     BLOCKS AVG_ROW_LEN SAMPLE_SIZE LAST_ANALYZED       SAVE_TIME
------- ------ ----------- ---------- ---------- ----------- ----------- ------------------- -------------------
PENDING TEST   TEST              1000          5          14        1000 2015-03-01 17:20:09 3000-12-01 01:00:00

STAT    OWNER  TABLE_NAME    NUM_ROWS     BLOCKS AVG_ROW_LEN SAMPLE_SIZE LAST_ANALYZED
------- ------ ----------- ---------- ---------- ----------- ----------- -------------------
CURRENT TEST   TEST

SAVE_TIME=「3000-12-01 01:00:00」となるデータ件数1,000件の保留統計情報が格納されていることが確認できます。

3. データ1,000件インサート(計2,000件)および統計情報の取得(2回目)

さらに1,000件インサートして、公開属性が「保留」のまま、再度統計情報を取得しましょう。

SQL> begin
  2   for i in 1001..2000 loop
  3    insert into test values (i,'TEST DATA');
  4    commit;
  5   end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

SQL> select count(*) from test;

  COUNT(*)
----------
      2000

SQL> begin
  2   DBMS_STATS.GATHER_TABLE_STATS('TEST','TEST');
  3  end;
  4  /

PL/SQL procedure successfully completed.

SQL> @showstat

STAT    OWNER  TABLE_NAME    NUM_ROWS     BLOCKS AVG_ROW_LEN SAMPLE_SIZE LAST_ANALYZED       SAVE_TIME
------- ------ ----------- ---------- ---------- ----------- ----------- ------------------- -------------------
PENDING TEST   TEST              2000         13          14        2000 2015-03-01 17:22:40 3000-12-01 01:00:00

STAT    OWNER  TABLE_NAME    NUM_ROWS     BLOCKS AVG_ROW_LEN SAMPLE_SIZE LAST_ANALYZED
------- ------ ----------- ---------- ---------- ----------- ----------- -------------------
CURRENT TEST   TEST

SAVE_TIME=「3000-12-01 01:00:00」となるデータ件数2,000件の保留統計情報が格納されていることが確認できます。
つまり、先ほどの保留統計情報は上書きされ1件のみが保持されています。保留統計情報は履歴を持たないようです。

4. データ1,000件インサート(計3,000件)および統計情報の取得(3回目)

念のため、もう一度1,000件インサートして統計情報を取得してみます。

SQL> begin
  2   for i in 2001..3000 loop
  3    insert into test values (i,'TEST DATA');
  4    commit;
  5   end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

SQL> select count(*) from test;

  COUNT(*)
----------
      3000

SQL> begin
  2   DBMS_STATS.GATHER_TABLE_STATS('TEST','TEST');
  3  end;
  4  /

PL/SQL procedure successfully completed.

SQL> @showstat

STAT    OWNER  TABLE_NAME    NUM_ROWS     BLOCKS AVG_ROW_LEN SAMPLE_SIZE LAST_ANALYZED       SAVE_TIME
------- ------ ----------- ---------- ---------- ----------- ----------- ------------------- -------------------
PENDING TEST   TEST              3000         13          14        3000 2015-03-01 17:24:57 3000-12-01 01:00:00

STAT    OWNER  TABLE_NAME    NUM_ROWS     BLOCKS AVG_ROW_LEN SAMPLE_SIZE LAST_ANALYZED
------- ------ ----------- ---------- ---------- ----------- ----------- -------------------
CURRENT TEST   TEST

やはりSAVE_TIME=「3000-12-01 01:00:00」となるデータ件数3,000件の保留統計情報で上書きされていることがわかります。

保留統計情報はこのように1件のみが保持されるので、公開統計情報のように履歴データがSYSAUX表領域を圧迫する危険性がありません
実際の運用で「オプティマイザ統計の保留」をどのように活用したら良いのかということはこのシリーズの最後でまとめる予定ですが、以上のように領域管理上のメリットということはあまり認識されていないことかもしれません。

次回は、スキーマ単位、テーブル単位で公開属性を設定する場合の動きを見ていきます。

続く