忘れかけのIT備忘録

今まで学んできた知識や小技、なるほど!と思ったことをメモするブログです。

OracleのSQLの動き(ハードパースとソフトパース)

今回はOracleSQLの具体的な処理の流れについて調査しました。

SQLは主に下記の順番で処理されます。
SQLの発行→カーソルオープン→SQL文の解析(文法チェック、解析、実行計画の作成など)→SQL文の実行→(フェッチ)→カーソルクローズ
※フェッチはSELECTのみ
SQL文の発行から実行されるまでの流れを見てみます。
前提として「専用サーバモード」、「コストベースオプティマイザ」を使用する場合です。

SQLの解析
SQL文の各単語を各チェック処理で処理できるデータ構造に分解し、構文チェック、意味チェック、共有プールチェックを行います
アプリケーションから発行されたときに初めてSQL文を解析するため、解析回数を削減できるのはアプリケーション側のみです(Oracle側ではできません)

構文チェック
スペルミスなどSQL文の文法をチェックします
一般的なコンパイルでいう「構文解析
いわゆる「字句解析」もこのフェーズでやっているのでしょうか?

(例)
SQL> SELECT * FORM employees;
SELECT * FORM employees
         *
ERROR at line 1:
ORA-00923: FROM keyword not found where expected

意味チェック
SQL文で指定されたオブジェクトが存在するかなどSQL文の意味が有効かチェックします
一般的なコンパイルでいう「意味解析」
SQL文を実行する権限チェックもこのフェーズでやっているのでしょうか?

(例)
SQL> SELECT * FROM nonexistent_table;
SELECT * FROM nonexistent_table
              *
ERROR at line 1:
ORA-00942: table or view does not exist

共有プールチェック

発行されたSQL文がライブラリキャッシュの共有SQL領域(カーソルキャッシュ)の既存のSQL文と同じか確認します
厳密にはSQL文ごとに13桁のハッシュ値(SQL ID)を生成し、既存の解析済みのSQL文に同じハッシュ値があるか確認します
ハッシュ値を見てソフトパースを実行するかハードパースを実行するか判断します

ハッシュ値が異なる場合(共有SQL領域に同じSQL文が存在しなかった場合)
ハードパース(ハード解析)が実行され、「SQLの最適化」へ遷移する
ハードパースは実行するアプリケーションコード(プログラム)を新しく生成する(ライブラリ・キャッシュ・ミス)
※再利用できるSQL文がない(=再利用できるアプリケーションコード(プログラム)がない)ため、「SQLの実行」で実行するアプリケーションコード(プログラム)を新しく生成する必要がある
ライブラリキャッシュやデータディクショナリキャッシュへ何度もアクセス(メタデータのフェッチ)するため、大量のラッチ(Oracleの内部ロック)が発生する
ソフトパースに比べ、ディスクI/OやCPU使用量が増えるため、処理時間が掛かる

ハッシュ値が同じ場合(共有SQL領域に同じSQL文が存在した場合)
ソフトパース(ソフト解析)が実行され、「SQLの実行」へ遷移する
ソフトパースではSQL文(実行するアプリケーションコード(プログラム))を再利用する(ライブラリ・キャッシュ・ヒット)
なお、同じSELECT文でも「select」、「SELECT」ではハッシュ値が異なるため、異なるSQL文とみなされる
また、下記のようにまったく同じSQL文でもスキーマが異なる場合、別の意味になってくるため、ソフトパースにならないケースもある(ハードパースに切り替わる)
(例)
hogeスキーマ内で select * from emp;
fugaスキーマ内で select * from emp;
SQLの最適化フェーズと行ソースジェネレータによる行ソース生成を省略できるため、ハードパースに比べて優れている
同じセッションまたは異なるセッションにある複数のプライベートSQL領域は、単一の共有SQL領域を参照できる(カーソル共有)

SQLの最適化
オプティマイザが複数の実行計画を作成し、複数の実行計画の中から最適(最小コスト)な実行計画を選択して行ソースジェネレータへ渡します
DMLはハード解析が実施される場合、必ず最適化されますが、DDLは一部を除いて最適化されません(DDLDMLとは異なる手順で実行されるため)

DDLは最適化を要求する副問合せなどのDMLコンポーネントを含む(CTASなど)場合、最適化されます
検証中に実行計画の出力有無を試しました。
--実行計画の出力なし
TRUNCATE TABLE、DROP TABLE、CREATE TABLE、CREATE INDEX

--実行計画の出力あり
CTAS(CREATE TABLE XXX AS SELECT)、CREATE UNIQUE INDEX

SQLの行ソース生成
オプティマイザから受け取った最適な実行計画を基に行ソースジェネレータがSQLの実行に使用する実行計画(SQLの実行に必要なデータ構造(行ソースツリー))を出力します
行ソースツリーには下記の情報が含まれています
SQL文によって参照される表の順序
 →たとえば外部表はDEPT表、内部表はEMP表など
SQL文で言及される各表へのアクセス方法
 →たとえばDEPT表へのアクセスは索引スキャン(INDEX INIQUE SCAN)、EMP表へのアクセスは索引スキャン(INDEX RANGE SCAN)など
SQL文の結合操作の影響を受ける表の結合方法
 →たとえばDEPT表とEMP表はネステッドループ結合するなど
・フィルタ、ソートまたは集計などのデータ操作
 →たとえばDEPT表のDEPTNO=10でフィルタし、DEPT表のDEPTNOとEMP表のDEPTNOで結合するなど
※これらの情報を保持つするのは行ソースツリーですが、表の順序やアクセス方法を決めるのはオプティマイザです

(例)行ソースジェネレータが出力する実行計画

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  05wg5gppgxj6w, child number 0
-------------------------------------
SELECT /* XPLAN_TEST1 */ E.EMPNO, E.ENAME, D.DEPTNO, D.DNAME FROM DEPT
D, EMP E WHERE D.DEPTNO = E.DEPTNO AND D.DEPTNO = 10

Plan hash value: 1561127466

--------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name            | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |                 |       |       |     1 (100)|          |
|   1 |  NESTED LOOPS                        |                 |     3 |   165 |     1   (0)| 00:00:01 |
|   2 |   TABLE ACCESS BY INDEX ROWID        | DEPT            |     1 |    22 |     1   (0)| 00:00:01 |
|*  3 |    INDEX UNIQUE SCAN                 | PK_DEPT         |     1 |       |     1   (0)| 00:00:01 |
|   4 |   TABLE ACCESS BY INDEX ROWID BATCHED| EMP             |     3 |    99 |     0   (0)|          |
|*  5 |    INDEX RANGE SCAN                  | IDX1_EMP_DEPTNO |     3 |       |     0   (0)|          |
--------------------------------------------------------------------------------------------------------

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

   3 - access("D"."DEPTNO"=10)
   5 - access("E"."DEPTNO"=10)

 

SQLの実行
行ソースジェネレータによって作成された行ソースツリー内の各行ソースをSQLエンジンが実行します
DDLDMLはこのフェーズで処理を終了します

フェッチ(SELECTのみ)
データを取り出し、サーバプロセスへ渡します

【補足】
プライベートSQL領域
SQLを実行するために必要な情報が格納される
持続領域、ランタイム領域がある

・持続領域
バインド変数の値などSQL文の実行時、SQL文に指定されるものが格納される
カーソルクローズ時に解放される

ランタイム領域
問合せ実行状態情報(たとえば全表スキャンで現在までに取得された行数など)が格納される
DMLだとSQL文が終了するとランタイム領域が解放される

オープンカーソル
カーソルがオープンしている場合、解析処理しないでカーソルハンドルを使用できる

クローズカーソル
SQL処理が完了すると通常はカーソルをクローズする
クローズ後もプライベートSQL領域にはカーソルが残るため、再度オープンすれば即時にカーソルを使用できる
キャッシュするカーソル数はSESSION_CACHED_CURSORS初期化パラメータで設定できる

セッションメモリー
セッション変数(ログイン情報)やセッションに関する情報を格納する

作業領域
結合や並べ替えなどSQLの処理でメモリ操作が発生するときに必要に応じて使う
ソート領域、ハッシュ結合領域、スタック領域がある

・ソート領域
ORDER BY、GROUP BY、ROLLUPなどで使用する

・ハッシュ結合領域
ハッシュ結合で使用する

・ビットマップ操作領域
ビットマップマージ、ビットマップ作成などで使用する

スタック領域
ユーザーセッションで使用されるローカル変数を保持する領域

ライブラリキャッシュ
共有SQL、共有PL/SQL領域、オブジェクトのメタデータなどが格納される
SQL実行時、カーソル(ポインタ)を使用して操作が行われる

ディクショナリキャッシュ
データディクショナリから取得したレコードが格納される

結果キャッシュ(11g以降)
SELECT文の結果レコードを格納する
バッファキャッシュにアクセスせずに使用頻度の高いレコードを再利用できる

SQLの最適化」でオプティマイザが実施する処理をもう少し詳しく見てみます。

問合せトランスフォーマ
パーサから受け取ったSQL(構文解析フェーズや意味解析フェーズで文法チェックや権限チェックの完了したSQL)に対し、選択可能な実行計画を増やすため、さらに効率的に実行できる可能性がある別のSQLに書き換えます
たとえばビューや副問合せを展開して通常の結合に変換するなど

エスティメータ
オプティマイザ統計や動的サンプリング(動的統計)で収集された統計情報をデータディクショナリから参照して変換済みSQLに対する実行計画の実行コスト値を算出します

プランジェネレータ
実行計画を生成します
ただし、データアクセスパス、テーブルの結合順序、テーブルの結合方法の組み合わせを見て、さらに実行コスト値の小さい実行計画が生成できる可能性がある場合、エスティメータへ遷移する

■参考資料
メモリー・アーキテクチャ
カーソル共有によるReal-World Performanceの改善
SQLの処理
V$SQLAREA
V$SQL
パフォーマンス改善と事前対策に役立つ Oracle SQLチューニング 本気で学ぶ実践的な考え方とテクニック
新・門外不出のOracle現場ワザ エキスパートが明かす運用・管理の極意
オラクルマスター教科書 ORACLE MASTER Expert パフォーマンス・チューニング編

■おわりに
共有プール内のカーソルは親カーソルと子カーソルに分けられます。(同じSQL文でも実行計画が異なるパターンに対応するため)
親カーソルにはSQLテキストが格納されます(V$SQLAREAで確認可能)
子カーソルには生成された解析結果(実行計画、バインド変数の値など)が格納されます(V$SQLで確認可能)

【検証環境】
OS:Oracle Linux 6.5
DB/GI:Oracle Database 12c Release 1 (12.1.0.2.0) Enterprise Edition
※2ノードRAC(管理者管理型DB)

【検証手順】
1. SCOTTユーザでEMPテーブル取得(HRユーザとは別セッション)
2. HRユーザでEMPテーブル取得(SCOTTユーザとは別セッション)
3. カーソル情報確認

【作業ログ】

1. SCOTTユーザでEMPテーブル取得(HRユーザとは別セッション)
SQL> SELECT /* TEST */ * FROM EMP WHERE EMPNO = 7369;

     EMPNO ENAME           JOB                    MGR HIREDATE                   SAL       COMM     DEPTNO
---------- --------------- --------------- ---------- ------------------- ---------- ---------- ----------
      7369 SMITH           CLERK                 7902 1980-12-17 00:00:00        800                    20

2. HRユーザでEMPテーブル取得(SCOTTユーザとは別セッション)
SQL> SELECT /* TEST */ * FROM EMP WHERE EMPNO = 7369;

     EMPNO ENAME            JOB                    MGR HIREDATE                   SAL       COMM     DEPTNO
---------- ---------------- --------------- ---------- ------------------- ---------- ---------- ----------
      7369 SMITH            CLERK                 7902 1980-12-17 00:00:00        800                    20

3. カーソル情報確認
SQL> select SQL_ID, SQL_TEXT, VERSION_COUNT, PARSING_USER_ID, PARSING_SCHEMA_NAME, CPU_TIME, DISK_READS from v$sqlarea where sql_text like '%TEST%' ORDER BY FIRST_LOAD_TIME DESC;

SQL_ID          SQL_TEXT                                                     VERSION_COUNT PARSING_USER_ID PARSING_SCHEMA_   CPU_TIME DISK_READS
--------------- ------------------------------------------------------------ ------------- --------------- --------------- ---------- ----------
731pcxytcvana   select SQL_ID, SQL_TEXT, VERSION_COUNT, PARSING_USER_ID, PAR             1               0 SYS                   9000          1
                SING_SCHEMA_NAME, CPU_TIME, DISK_READS from v$sqlarea where
                sql_text like '%TEST%' ORDER BY FIRST_LOAD_TIME DESC

7z1q049gfa0n6   SELECT /* TEST */ * FROM EMP WHERE EMPNO = 7369                          2             102 HR                   14000         24
SQL_ID:SQL文の識別子
SQL_TEXT:SQL
※VERSION_COUNT:子カーソルの数
※PARSING_USER_ID:子カーソルの解析に使用されたスキーマID
※PARSING_SCHEMA_NAME:子カーソルの解析に使用されたスキーマ
※CPU_TIME:CPU時間(マイクロ秒)
※DISK_READS:ディスク読取り回数

SQL> select SQL_ID, CHILD_NUMBER, PLAN_HASH_VALUE, PARSING_USER_ID, PARSING_SCHEMA_NAME, CPU_TIME, DISK_READS from v$sql where sql_id= '7z1q049gfa0n6';

SQL_ID          CHILD_NUMBER PLAN_HASH_VALUE PARSING_USER_ID PARSING_SCHEMA_   CPU_TIME DISK_READS
--------------- ------------ --------------- --------------- --------------- ---------- ----------
7z1q049gfa0n6              0      2949544139             110 SCOTT                 8000         15
7z1q049gfa0n6              1      2949544139             102 HR                    6000          9
※CHILD_NUMBER:子カーソルの番号
※PLAN_HASH_VALUE:実行計画の識別子
★SCOTTユーザとHRユーザで同じSQL文を実行したが、スキーマが異なるため、子カーソルが別々で作成された

OracleのSQLの動き(参照と更新)

今回はOracleSQLの動きを調査しました。
参照系はSELECT、更新系はUPDATEを使用してSQLの動きを見てみました。

SQLの処理の流れ
SQLが実行されると主に3つのフェーズに分けて処理されます。

PARSE(解析フェーズ)
SQL文の構文、意味の妥当性も含めた文法チェック
SQL文の実行権限の有無チェック
SQL文の解析済み情報がライブラリキャッシュに存在するかチェック
※キャッシュに存在しない場合、ライブラリキャッシュに解析済みの情報を格納する
 このフェーズでSQL文の実行計画も作成される

EXECUTE(実行フェーズ)
PARSE(解析フェーズ)で生成されたSQL文の実行計画をもとにSQL文を処理する

FETCH(フェッチフェーズ) ※SELECT文のみ
参照結果の行が選択され、ソート処理が必要な場合は順序付けされる

参照
たとえば「A」というデータをSELECTするときの流れを見てみます

SQL文(SELECT文)を発行
② 発行されたSQL文を解析し、共有プール(ライブラリキャッシュ)にSQL文の解析情報(実行計画など)を格納する(解析フェーズ)
   ライブラリキャッシュに解析済みSQL文が存在する場合、解析を終了し、ライブラリキャッシュの解析済み結果を再利用する
③ 解析フェーズで生成された実行計画を使用してSQLを実行し、「A」へアクセスする
   「A」がデータベースバッファキャッシュに存在する場合、キャッシュから「A」を取得する
   データベースバッファキャッシュに存在しない場合、サーバプロセスがデータファイルから「A」を取得し、キャッシュに格納後、キャッシュから「A」を取得する
④ ③で取得した「A」をサーバプロセスへ渡す
⑤ 検索結果(「A」)をユーザプロセスへ渡す

更新
たとえば「A」というデータを「B」にUPDATEするときの流れを見てみます

SQL文(UPDATE文)を発行
② 発行されたSQL文を解析し、共有プール(ライブラリキャッシュ)にSQL文の解析情報(実行計画など)を格納する(解析フェーズ)
   ライブラリキャッシュに解析済みSQL文が存在する場合、解析を終了し、ライブラリキャッシュの解析済み結果を再利用する
③ 解析フェーズで生成された実行計画を使用してSQLを実行し、「A」へアクセスする
   「A」がデータベースバッファキャッシュに存在する場合、キャッシュから「A」を取得する
   データベースバッファキャッシュに存在しない場合、サーバプロセスがデータファイルから「A」を取得し、キャッシュに格納後、キャッシュから「A」を取得する
④ 更新履歴(「A」→「B」)をREDOログバッファに書き込む
   LGWRプロセスは非同期でREDOログバッファの更新履歴をREDOログファイルに書き込む
   ※UNDOデータ(更新前データ)をUNDOセグメントに書き込む
⑤ データベースバッファキャッシュの「A」を「B」に更新する
⑥ 更新結果をユーザプロセスへ渡す(サーバプロセスはLGWRの処理完了は待たない)

更新(COMMIT)

SQL文(COMMIT文)を発行
② 発行されたSQL文を解析し、共有プール(ライブラリキャッシュ)にSQL文の解析情報(実行計画など)を格納する(解析フェーズ)
   ライブラリキャッシュに解析済みSQL文が存在する場合、解析を終了し、ライブラリキャッシュの解析済み結果を再利用する
③ サーバプロセスがCOMMITをREDOログバッファに書き込む
④ LGWRプロセスはREDOログバッファのCOMMITをREDOログファイルに書き込む
   REDOログバッファの更新履歴(「A」→「B」)がREDOログファイルに書き込まれていない場合、更新履歴を書き込んでからCOMMITを書き込む
   DBWnプロセスはCOMMITとは非同期で更新内容(「B」)をデータファイルに書き込む
⑤ LGWRプロセスはCOMMITの書き込み完了をサーバプロセスへ通知する
⑥ サーバプロセスがCOMMIT結果をユーザプロセスへ渡す(サーバプロセスはLGWRの処理完了を待つ)

■参考資料
絵で見てわかるOracleの仕組み
パフォーマンス改善と事前対策に役立つ Oracle SQLチューニング 本気で学ぶ実践的な考え方とテクニック
新・門外不出のOracle現場ワザ エキスパートが明かす運用・管理の極意

■おわりに
普段の業務で使用しているSELECTやUPDATEなどのSQLの挙動を復習する良い機会になりました。
挙動を知っているからと言ってSQLの性能改善ができるようになるかと言えば難しいですが、SQLを使用する人(特にアプリケーション開発者)は知っておいて損は無いと思います。

Oracleの統計情報(クラスタ化係数編)

今回はOracleクラスタ化係数について調査しました。

Oracleの統計情報 - 忘れかけのIT備忘録で少しだけ触れましたが、クラスタ化係数は索引の列データと表の列データの分布度(データの格納順がどれくらい異なるかの指標)です。
索引の列データに格納されているデータ(キー値)と実際の表の列データに格納されているデータ(キー値)の順番がどれくらい揃っているか、バラツキがあるかを表します。
データの格納順がだいたい揃っている(比較的一致している)場合、クラスタ化係数は小さくなります
データの格納順がほとんど揃っていない(バラツキがある)場合、クラスタ化係数は大きくなります
係数の値によってデータアクセスのオペレーションが変わってきます(係数が小さい場合、TABLE ACCESS BY INDEX ROWIDが採用されやすくなりますが、係数が大きい場合、索引スキャンより全表スキャンの方が効率が良いと判断される可能性があります)
クラスタ化係数は、XXX_IND_STATISTICS、XXX_INDEXESのCLUSTERING_FACTOR列で確認できます。

クラスタ化係数の計算方法
リーフブロックの隣り合ったレコードが、異なる表ブロックの列データへのポインタを持つ場合、カウントアップします
極端に言うとリーフブロックに格納されている列データが異なる表ブロックに格納されている場合、カウントアップするイメージです
あくまで想定ですが参考資料を読むうちに法則みたいなものがあるのではと思いました。

(パターン①)隣接する列データが同じ索引ブロックにあり、同じ表ブロックにある   → カウントアップしない
(パターン②)隣接する列データが同じ索引ブロックにあるが、異なる表ブロックにある → カウントアップする
(パターン③)隣接する列データが異なる索引ブロックにあるが、同じ表ブロックにある → カウントアップしない
(パターン④)隣接する列データが異なる索引ブロックにあり、異なる表ブロックにある → カウントアップする

クラスタ化係数が小さい場合
たとえば下記の図で、EMPNO「1001~1004」の表データにアクセスする場合、合計3ブロックアクセスになります(索引ブロックアクセス:2ブロック、表ブロックアクセス:1ブロック)
クラスタ化係数は「4」になるはずです(トレース表を参照)
→索引スキャン(INDEX XXX SCAN、TABLE ACCESS BY INDEX ROWID)が採用されやすくなります

クラスタ化係数が大きい場合
たとえば下記の図で、EMPNO「1001~1004」の表データにアクセスする場合、合計6ブロックアクセスになります(索引ブロックアクセス:2ブロック、表ブロックアクセス:4ブロック)
クラスタ化係数は「15」になるはずです(トレース表を参照)
→全表スキャン(TABLE ACCESS FULL)が採用されやすくなります(表内の大半のデータにアクセスするため)

■参考資料
https://www.oracle.com/technetwork/jp/ondemand/branch/120328-cbo-1593994-ja.pdf
パフォーマンス改善と事前対策に役立つ Oracle SQLチューニング 本気で学ぶ実践的な考え方とテクニック

■おわりに
クラスタ化係数は小さい方が良いか、大きい方が良いかという疑問も出てきますが(関連資料が見つけられませんでした)、よくよく考えてみるとクラスタ化係数はあくまでオプティマイザが索引スキャンか全表スキャンかの指標にするものなので一概にどちらが良いとは言えないと思いました。(図を描く上では小さい方が助かりますが。。)

Oracleの統計情報(ヒストグラム編)

今回はOracleの統計情報のうち、ヒストグラムについて調査しました。

Oracleの統計情報 - 忘れかけのIT備忘録で少しだけ触れましたが、ヒストグラムは表の列データの分布状況です。
セレクティビティ(行の選択率)のコストを見積もるために使用される統計情報です。
通常の統計情報に比べ、より最適な実行計画を作成/選択するのが目的です。
ヒストグラムには、頻度分布ヒストグラム、高さ調整ヒストグラム、上位頻度ヒストグラム、ハイブリッドヒストグラムがあります。
XXX_TAB_STATISTICSのHISTOGRAMで使用しているヒストグラムを確認できます。
XXX_HISTOGRAMS(XXX_TAB_HISTOGRAMS)でヒストグラムの詳細を確認できます。
※セレクティビティ:行が選択される割合。全表スキャンか索引スキャンかの判断指標になる
※上位頻度ヒストグラム、ハイブリッドヒストグラムは12c以降の機能

ヒストグラムがないと・・・
Oracleはデフォルトでヒストグラムを収集しますが、ヒストグラムが収集されていない場合、オプティマイザはデータが均等に分布されていると推測してしまいます。

実際のテーブルのデータの分布とオプティマイザが把握しているデータの分布に誤差が発生するため、最適な実行計画を作成/選択できません。
たとえば下記の図の場合、「D」のデータへアクセスする際、オプティマイザは「NDV=4」、「データは均等に分布されている」と見積もって全表スキャンの方が効率的と判断してしまう可能性があります。

【検証手順】
1. 検証テーブル作成、検証データ挿入
2. 統計情報取得(ヒストグラム収集なし)
3. ヒストグラムが存在しない状態でレコード取得、実行計画確認
4. 統計情報取得(ヒストグラム収集あり)
5. ヒストグラムが存在する状態でレコード取得、実行計画確認

【作業ログ】

1. 検証テーブル作成、検証データ挿入
SQL> CREATE TABLE TEST_TABLE1
  2         (COL1 NUMBER(7) CONSTRAINT PK_TEST_TABLE1 PRIMARY KEY,
  3          COL2 VARCHAR2(4));

表が作成されました。

SQL> CREATE INDEX IDX1_TEST_TABLE1 ON TEST_TABLE1(COL2);

索引が作成されました。

SQL> INSERT INTO TEST_TABLE1 SELECT LEVEL, 'A' FROM DUAL CONNECT BY LEVEL <= 1000;

1000行が作成されました。

SQL> INSERT INTO TEST_TABLE1 SELECT LEVEL + 1000, 'B' FROM DUAL CONNECT BY LEVEL <= 1000;

1000行が作成されました。

SQL> INSERT INTO TEST_TABLE1 SELECT LEVEL + 2000, 'C' FROM DUAL CONNECT BY LEVEL <= 1000;

1000行が作成されました。

SQL> INSERT INTO TEST_TABLE1 VALUES(3001, 'D');

1行が作成されました。

SQL> COMMIT;

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

SQL> SELECT COL2, COUNT(*) FROM TEST_TABLE1 GROUP BY COL2 ORDER BY 1;

COL2           COUNT(*)
------------ ----------
A                  1000
B                  1000
C                  1000
D                     1

2. 統計情報取得(ヒストグラム収集なし)
SQL> BEGIN
  2    DBMS_STATS.GATHER_TABLE_STATS(ownname    => 'SYS',
  3                                  tabname    => 'TEST_TABLE1',
  4                                  method_opt => 'FOR ALL COLUMNS SIZE 1',
  5                                  cascade    => FALSE);
  6  END;
  7  /

PL/SQLプロシージャが正常に完了しました。
★'FOR ALL COLUMNS SIZE 1'はヒストグラムを収集しないように明示的に指定している

SQL> SELECT OWNER, TABLE_NAME, COLUMN_NAME, NUM_DISTINCT, NUM_BUCKETS, LAST_ANALYZED, HISTOGRAM FROM ALL_TAB_COL_STATISTICS WHERE OWNER = 'SYS' AND TABLE_NAME = 'TEST_TABLE1' AND COLUMN_NAME = 'COL2';

OWNER           TABLE_NAME      COLUMN_NAME     NUM_DISTINCT NUM_BUCKETS LAST_ANALYZED       HISTOGRAM
--------------- --------------- --------------- ------------ ----------- ------------------- --------------------
SYS             TEST_TABLE1     COL2                       4           1 2023-02-17 11:19:30 NONE
ヒストグラムは収集されていない

3. ヒストグラムが存在しない状態でレコード取得、実行計画確認
SQL> SELECT /* TEST1_1 */ * FROM TEST_TABLE1 WHERE COL2 = 'D';

      COL1 COL2
---------- ------------
      3001 D

SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('3nazjkcxu3x77'));

PLAN_TABLE_OUTPUT
-----------------
SQL_ID  3nazjkcxu3x77, child number 0
-------------------------------------
SELECT /* TEST1_1 */ * FROM TEST_TABLE1 WHERE COL2 = 'D'

Plan hash value: 3636527845

--------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                  |       |       |     1 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| TEST_TABLE1      |   750 |  4500 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | IDX1_TEST_TABLE1 |     1 |       |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------

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

   2 - access("COL2"='D')
★インデックスを範囲検索して1行ヒットしたがオプティマイザは750行のテーブルアクセス(マルチブロックリードorパラレルリード)が発生すると見積もった

4. 統計情報取得(ヒストグラム収集あり)
SQL> BEGIN
  2    DBMS_STATS.GATHER_TABLE_STATS(ownname          => 'SYS',
  3                                  tabname          => 'TEST_TABLE1',
  4                                  estimate_percent => 100,
  5                                  method_opt       => 'FOR COLUMNS COL2 SIZE AUTO',
  6                                  cascade          => TRUE);
  7  END;
  8  /

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

SQL> SELECT OWNER, TABLE_NAME, COLUMN_NAME, NUM_DISTINCT, NUM_BUCKETS, LAST_ANALYZED, HISTOGRAM FROM ALL_TAB_COL_STATISTICS WHERE OWNER = 'SYS' AND TABLE_NAME = 'TEST_TABLE1' AND COLUMN_NAME = 'COL2';

OWNER           TABLE_NAME      COLUMN_NAME     NUM_DISTINCT NUM_BUCKETS LAST_ANALYZED       HISTOGRAM
--------------- --------------- --------------- ------------ ----------- ------------------- --------------------
SYS             TEST_TABLE1     COL2                       4           4 2023-02-17 11:20:21 FREQUENCY
ヒストグラムが収集された

5. ヒストグラムが存在する状態でレコード取得、実行計画確認
SQL> SELECT /* TEST1_2 */ * FROM TEST_TABLE1 WHERE COL2 = 'D';

      COL1 COL2
---------- ------------
      3001 D

SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('dm1pa05nmvdd7'));

PLAN_TABLE_OUTPUT
-----------------
SQL_ID  dm1pa05nmvdd7, child number 0
-------------------------------------
SELECT /* TEST1_2 */ * FROM TEST_TABLE1 WHERE COL2 = 'D'

Plan hash value: 3636527845

--------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                  |       |       |     2 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| TEST_TABLE1      |     1 |     6 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | IDX1_TEST_TABLE1 |     1 |       |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------

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

   2 - access("COL2"='D')
★インデックスを範囲検索して1行ヒットしてオプティマイザは1行のテーブルアクセスが発生すると見積もった

 

頻度分布ヒストグラム(Frequency)
列内の個別値がバケット数以下の場合、それぞれの値が何行あるのか正確に把握します(データの分布を列値の累計行数の差分から正確に計算して把握します)
個別値の数が指定されたバケット数以下の場合、頻度分布ヒストグラムを生成します。
「NDV <= バケット数」の場合、頻度分布ヒストグラムが採用されます。
バケット:分布データ(個別値)の格納領域

メリット
・NDVが少ない中、他の個別値よりも極端に累計行数が少ない個別値を検索する場合、索引アクセスが選択される可能性が高くなる
・個別値の累計行数の差分を算出するため、正確な分布データが得られる(それぞれの個別値が何行あるか正確に把握できる)
デメリット
・NDVに比例してデータ量が多くなるため、ヒストグラム格納用のディクショナリが肥大化しやすい
・最大2048種類の値までしかヒストグラムが作成できない(11gは最大254種類まで)

【検証手順】
前提として下記のようなデータがあるとします
SQL> select * from emp;

     EMPNO ENAME           JOB                    MGR HIREDATE                   SAL       COMM     DEPTNO
---------- --------------- --------------- ---------- ------------------- ---------- ---------- ----------
      7369 SMITH           CLERK                 7902 1980-12-17 00:00:00        800                    10
      7499 ALLEN           SALESMAN              7698 1981-02-20 00:00:00       1600        300         10
      7521 WARD            SALESMAN              7698 1981-02-22 00:00:00       1250        500         10
      7566 JONES           MANAGER               7839 1981-04-02 00:00:00       2975                    10
      7654 MARTIN          SALESMAN              7698 1981-09-28 00:00:00       1250       1400         10
      7698 BLAKE           MANAGER               7839 1981-05-01 00:00:00       2850                    10
      7782 CLARK           MANAGER               7839 1981-06-09 00:00:00       2450                    10
      7839 KING            PRESIDENT                  1981-11-17 00:00:00       5000                    10
      7844 TURNER          SALESMAN              7698 1981-09-08 00:00:00       1500          0         20
      7900 JAMES           CLERK                 7698 1981-12-03 00:00:00        950                    20
      7902 FORD            ANALYST               7566 1981-12-03 00:00:00       3000                    30
      7934 MILLER          CLERK                 7782 1982-01-23 00:00:00       1300                    40

SQL> select * from dept;

    DEPTNO DNAME           LOC
---------- --------------- ---------------
        10 ACCOUNTING      NEW YORK
        20 RESEARCH        DALLAS
        30 SALES           CHICAGO
        40 OPERATIONS      BOSTON

1. 統計情報取得(ヒストグラム収集なし)
2. 統計情報取得(ヒストグラム収集あり)

【作業ログ】

1. 統計情報取得(ヒストグラム収集なし)
SQL> BEGIN
  2    DBMS_STATS.GATHER_TABLE_STATS(ownname    => 'SCOTT',
  3                                  tabname    => 'EMP',
  4                                  method_opt => 'FOR ALL COLUMNS SIZE 1',
  5                                  cascade    => FALSE);
  6  END;
  7  /

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

SQL> SELECT OWNER, TABLE_NAME, COLUMN_NAME, NUM_DISTINCT, NUM_BUCKETS, LAST_ANALYZED, HISTOGRAM FROM ALL_TAB_COL_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME = 'EMP' AND COLUMN_NAME = 'DEPTNO';

OWNER           TABLE_NAME      COLUMN_NAME     NUM_DISTINCT NUM_BUCKETS LAST_ANALYZED       HISTOGRAM
--------------- --------------- --------------- ------------ ----------- ------------------- --------------------
SCOTT           EMP             DEPTNO                     4           1 2023-02-17 10:04:48 NONE
★通常の統計情報は取得されたがヒストグラムは存在しない

SQL> SELECT TABLE_NAME, COLUMN_NAME, ENDPOINT_VALUE, ENDPOINT_NUMBER FROM ALL_HISTOGRAMS  WHERE OWNER = 'SCOTT' AND TABLE_NAME = 'EMP' AND COLUMN_NAME = 'DEPTNO' ORDER BY ENDPOINT_VALUE;

TABLE_NAME      COLUMN_NAME     ENDPOINT_VALUE ENDPOINT_NUMBER
--------------- --------------- -------------- ---------------
EMP             DEPTNO                      10               0
EMP             DEPTNO                      40               1
ヒストグラムが存在しない場合、2バケット(列データの最小値・最大値)があるように見える

2. 統計情報取得(ヒストグラム収集あり)
SQL> BEGIN
  2    DBMS_STATS.GATHER_TABLE_STATS(ownname          => 'SCOTT',
  3                                  tabname          => 'EMP',
  4                                  estimate_percent => 100,
  5                                  method_opt       => 'FOR COLUMNS DEPTNO SIZE 2048',
  6                                  cascade          => TRUE);
  7  END;
  8  /

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

SQL> SELECT OWNER, TABLE_NAME, COLUMN_NAME, NUM_DISTINCT, NUM_BUCKETS, LAST_ANALYZED, HISTOGRAM FROM ALL_TAB_COL_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME = 'EMP' AND COLUMN_NAME = 'DEPTNO';

OWNER           TABLE_NAME      COLUMN_NAME     NUM_DISTINCT NUM_BUCKETS LAST_ANALYZED       HISTOGRAM
--------------- --------------- --------------- ------------ ----------- ------------------- --------------------
SCOTT           EMP             DEPTNO                     4           4 2023-02-17 10:05:40 FREQUENCY
★頻度分布ヒストグラムが取得された
★NDV=4に対して'FOR COLUMNS DEPTNO SIZE 2048'を指定してもバケット数(NUM_BUCKETS)は4になった。NDVより大きい値を指定してもバケット数はNDVと同じになるようです

SQL> SELECT TABLE_NAME, COLUMN_NAME, ENDPOINT_VALUE, ENDPOINT_NUMBER FROM ALL_HISTOGRAMS  WHERE OWNER = 'SCOTT' AND TABLE_NAME = 'EMP' AND COLUMN_NAME = 'DEPTNO' ORDER BY ENDPOINT_VALUE;

TABLE_NAME      COLUMN_NAME     ENDPOINT_VALUE ENDPOINT_NUMBER
--------------- --------------- -------------- ---------------
EMP             DEPTNO                      10               8
EMP             DEPTNO                      20              10
EMP             DEPTNO                      30              11
EMP             DEPTNO                      40              12
※ENDPOINT_VALUEバケットに格納する分布データ(個別値)
※ENDPOINT_NUMBER:バケットに格納する分布データ(個別値)の累計行数。累計行数と1つ前のエントリの累計行数の差分が列値の行数となる(オフセットみたいなイメージ)

 

高さ調整ヒストグラム(Height Balanced)
列内の個別値がバケット数より多い場合、指定バケット内にソートしたデータ(個別値)を順番・均等に配置して、それぞれのバケットの最大値を記録し、その値によってデータの偏りを把握します。
複数のバケットのうち、連続したバケットの最大値(ENDPOINT_VALUE)が同じ場合、その値が他の値より多く存在しているということになります。
複数のバケットの最大値を記録し、最も頻出度が高いものをポピュラ値と呼びます。
「NDV > バケット数」の場合、高さ調整ヒストグラムが採用されます。

メリット
・厳密な統計がなくても正しい実行計画が選択できる
・NDVが多い場合でもヒストグラムを使用できる
・ポピュラ値以外の個別値は省略できるため、頻度分布ヒストグラムより領域節約できる可能性がある
デメリット
・ポピュラ値でデータの偏りを見るため、等価条件では偏りを見つけられない(たとえばDEPTNOが全部「10」の場合、ポピュラ値は10となるため、データの偏りを把握できないということだと思います)
・頻度分布ヒストグラムより正確ではない

【検証手順】
1. 統計情報取得(ヒストグラム収集なし)
2. 統計情報取得(ヒストグラム収集あり)

【作業ログ】

1. 統計情報取得(ヒストグラム収集なし)
SQL> BEGIN
  2    DBMS_STATS.GATHER_TABLE_STATS(ownname    => 'SCOTT',
  3                                  tabname    => 'EMP',
  4                                  method_opt => 'FOR ALL COLUMNS SIZE 1',
  5                                  cascade    => FALSE);
  6  END;
  7  /

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

SQL> SELECT OWNER, TABLE_NAME, COLUMN_NAME, NUM_DISTINCT, NUM_BUCKETS, LAST_ANALYZED, HISTOGRAM FROM ALL_TAB_COL_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME = 'EMP' AND COLUMN_NAME = 'DEPTNO';

OWNER           TABLE_NAME      COLUMN_NAME     NUM_DISTINCT NUM_BUCKETS LAST_ANALYZED       HISTOGRAM
--------------- --------------- --------------- ------------ ----------- ------------------- --------------------
SCOTT           EMP             DEPTNO                     4           1 2023-02-17 10:04:48 NONE
★通常の統計情報は取得されたがヒストグラムは存在しない

SQL> SELECT TABLE_NAME, COLUMN_NAME, ENDPOINT_VALUE, ENDPOINT_NUMBER FROM ALL_HISTOGRAMS  WHERE OWNER = 'SCOTT' AND TABLE_NAME = 'EMP' AND COLUMN_NAME = 'DEPTNO' ORDER BY ENDPOINT_VALUE;

TABLE_NAME      COLUMN_NAME     ENDPOINT_VALUE ENDPOINT_NUMBER
--------------- --------------- -------------- ---------------
EMP             DEPTNO                      10               0
EMP             DEPTNO                      40               1

2. 統計情報取得(ヒストグラム収集あり)
SQL> BEGIN
  2    DBMS_STATS.GATHER_TABLE_STATS(ownname          => 'SCOTT',
  3                                  tabname          => 'EMP',
  4                                  estimate_percent => 100,
  5                                  method_opt       => 'FOR COLUMNS DEPTNO SIZE 3',
  6                                  cascade          => TRUE);
  7  END;
  8  /

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

SQL> SELECT OWNER, TABLE_NAME, COLUMN_NAME, NUM_DISTINCT, NUM_BUCKETS, LAST_ANALYZED, HISTOGRAM FROM ALL_TAB_COL_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME = 'EMP' AND COLUMN_NAME = 'DEPTNO';

OWNER           TABLE_NAME      COLUMN_NAME     NUM_DISTINCT NUM_BUCKETS LAST_ANALYZED       HISTOGRAM
--------------- --------------- --------------- ------------ ----------- ------------------- --------------------
SCOTT           EMP             DEPTNO                     4           3 2023-02-17 10:09:01 HEIGHT BALANCED
★高さ調整ヒストグラムが取得された

SQL> SELECT TABLE_NAME, COLUMN_NAME, ENDPOINT_VALUE, ENDPOINT_NUMBER FROM ALL_HISTOGRAMS  WHERE OWNER = 'SCOTT' AND TABLE_NAME = 'EMP' AND COLUMN_NAME = 'DEPTNO' ORDER BY ENDPOINT_VALUE;

TABLE_NAME      COLUMN_NAME     ENDPOINT_VALUE ENDPOINT_NUMBER
--------------- --------------- -------------- ---------------
EMP             DEPTNO                      10               2
EMP             DEPTNO                      40               3
※ENDPOINT_VALUEバケットに格納する分布データ(分布データの最大値)
※ENDPOINT_NUMBER:格納する分布データを格納するバケット番号

 

上位頻度ヒストグラム(12c~) ※未検証
頻度分布ヒストグラムから派生したヒストグラムで個別値の中で特に割合の多い個別値だけを対象としたヒストグラムです。
上位N個の個別値がデータの大半を占めている場合、上位頻度ヒストグラムが作成されます。
上位N個についてはオプティマイザが正確に行数を見積もることができます。

ハイブリッドヒストグラム(12c~) ※未検証
高さ調整ヒストグラムと頻度分布ヒストグラムを組み合わせたヒストグラムです。
高さ調整ヒストグラムの場合、ほぼポピュラな値について正確ではない見積りが作成される場合があります。
たとえば1つのバケットのエンドポイント値(最大値)として出現しますが、2つのバケットをほぼ占めているエンドポイント値(最大値)はポピュラ値とはみなされません。(ポピュラ値が見つからない)
この問題を解決するため、複数のバケットを占めるエンドポイント値(最大値)がないように個別値が配分され、エンドポイント繰返しカウント値が格納されます。
エンドポイント繰返しカウント値はヒストグラム内の各エンドポイント(バケット)について、エンドポイント値(最大値)が繰り返された回数です。
繰返しカウントを使用することで、オプティマイザはほぼポピュラな値について正確な見積りができます。

下記の手順でヒストグラムを作成します。

1. 高さ調整ヒストグラムのように各バケットに同数ずつ個別値を格納する
2. 頻度分布ヒストグラムのように同じ個別値が複数のバケットに格納されないよう調整する

バケットの最後の個別値が何行あるのか記録していき、頻出する個別値に対して正確な見積もりができます。

※図を作成しようとしましたがそもそも仕組みがイメージできませんでした。。

オプティマイザの勘違い
【検証手順】
1. 検証テーブル作成、検証データ挿入
2. 統計情報取得(ヒストグラム収集なし)
3. ヒストグラムが存在しない状態でレコード取得、実行計画確認
4. 統計情報取得(ヒストグラム収集あり)
5. ヒストグラムが存在する状態でレコード取得、実行計画確認

【作業ログ】

1. 検証テーブル作成、検証データ挿入
SQL> CREATE TABLE TEST_TABLE1
  2         (COL1 NUMBER(7) CONSTRAINT PK_TEST_TABLE1 PRIMARY KEY,
  3          COL2 VARCHAR2(4));

表が作成されました。

SQL> CREATE INDEX IDX1_TEST_TABLE1 ON TEST_TABLE1(COL2);

索引が作成されました。

SQL> INSERT INTO TEST_TABLE1 SELECT LEVEL, 'A' FROM DUAL CONNECT BY LEVEL <= 1000;

1000行が作成されました。

SQL> INSERT INTO TEST_TABLE1 SELECT LEVEL + 1000, 'B' FROM DUAL CONNECT BY LEVEL <= 1000;

1000行が作成されました。

SQL> INSERT INTO TEST_TABLE1 SELECT LEVEL + 2000, 'C' FROM DUAL CONNECT BY LEVEL <= 1000;

1000行が作成されました。

SQL> INSERT INTO TEST_TABLE1 VALUES(100000, 'D');

1行が作成されました。

SQL> COMMIT;

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

SQL> SELECT COL2, COUNT(*) FROM TEST_TABLE1 GROUP BY COL2 ORDER BY 1;

COL2           COUNT(*)
------------ ----------
A                  1000
B                  1000
C                  1000
D                     1

2. 統計情報取得(ヒストグラム収集なし)
SQL> BEGIN
  2    DBMS_STATS.GATHER_TABLE_STATS(ownname    => 'SYS',
  3                                  tabname    => 'TEST_TABLE1',
  4                                  method_opt => 'FOR ALL COLUMNS SIZE 1',
  5                                  cascade    => FALSE);
  6  END;
  7  /

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

SQL> SELECT OWNER, TABLE_NAME, COLUMN_NAME, NUM_DISTINCT, NUM_BUCKETS, LAST_ANALYZED, HISTOGRAM FROM ALL_TAB_COL_STATISTICS WHERE OWNER = 'SYS' AND TABLE_NAME = 'TEST_TABLE1' AND COLUMN_NAME = 'COL2';

OWNER           TABLE_NAME      COLUMN_NAME     NUM_DISTINCT NUM_BUCKETS LAST_ANALYZED       HISTOGRAM
--------------- --------------- --------------- ------------ ----------- ------------------- --------------------
SYS             TEST_TABLE1     COL2                       4           1 2023-02-17 11:19:30 NONE

3. ヒストグラムが存在しない状態でレコード取得、実行計画確認
SQL> SELECT /* TEST2_1 */ * FROM TEST_TABLE2 WHERE COL1 >= 50000;

      COL1 COL2
---------- ------------
    100000 D

SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('1xp5hx4tqgd68'));

PLAN_TABLE_OUTPUT
-----------------
SQL_ID  1xp5hx4tqgd68, child number 0
-------------------------------------
SELECT /* TEST2_1 */ * FROM TEST_TABLE2 WHERE COL1 >= 50000

Plan hash value: 775196657

---------------------------------------------------------------------------------
| Id  | Operation         | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |             |       |       |     3 (100)|          |
|*  1 |  TABLE ACCESS FULL| TEST_TABLE2 |  1502 |  9012 |     3   (0)| 00:00:01 |
---------------------------------------------------------------------------------

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

   1 - filter("COL1">=50000)
★COL1にはインデックスを作成しているが全表スキャンの方が効率的と判断された

4. 統計情報取得(ヒストグラム収集あり)
SQL> BEGIN
  2    DBMS_STATS.GATHER_TABLE_STATS(OWNNAME          => 'SYS',
  3                                  TABNAME          => 'TEST_TABLE2',
  4                                  ESTIMATE_PERCENT => 100,
  5                                  METHOD_OPT       => 'FOR COLUMNS COL1 SIZE AUTO',
  6                                  CASCADE          => TRUE);
  7  END;
  8  /

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

SQL> SELECT OWNER, TABLE_NAME, COLUMN_NAME, NUM_DISTINCT, NUM_BUCKETS, LAST_ANALYZED, HISTOGRAM FROM ALL_TAB_COL_STATISTICS WHERE OWNER = 'SYS' AND TABLE_NAME = 'TEST_TABLE2' AND COLUMN_NAME = 'COL1';

OWNER           TABLE_NAME      COLUMN_NAME     NUM_DISTINCT NUM_BUCKETS LAST_ANALYZED       HISTOGRAM
--------------- --------------- --------------- ------------ ----------- ------------------- --------------------
SYS             TEST_TABLE2     COL1                    3001         254 2023-02-17 11:25:52 HEIGHT BALANCED

5. ヒストグラムが存在する状態でレコード取得、実行計画確認
SQL> SELECT /* TEST2_2 */ * FROM TEST_TABLE2 WHERE COL1 >= 50000;

      COL1 COL2
---------- ------------
    100000 D

SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('5b1sk5jbpx7gx'));

PLAN_TABLE_OUTPUT
-----------------
SQL_ID  5b1sk5jbpx7gx, child number 0
-------------------------------------
SELECT /* TEST2_2 */ * FROM TEST_TABLE2 WHERE COL1 >= 50000

Plan hash value: 3003225609

------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name           | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                |       |       |     3 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| TEST_TABLE2    |     6 |    36 |     3   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | PK_TEST_TABLE2 |     6 |       |     2   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------------

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

   2 - access("COL1">=50000)
★索引スキャンが採用された

実データは合計3001行ですが「D」のEMPNOを「100000」にしたことでオプティマイザは合計100000行存在すると見積もってしまい、ヒストグラム収集前のSQL(TEST2_1)で全体の50%以上にアクセスするなら全表スキャンの方が効率的と判断してしまったようです

 

■参考資料
DBMS_STATS

ヒストグラム
https://www.oracle.com/technetwork/jp/ondemand/branch/120328-cbo-1593994-ja.pdf
移行前に知っておきたい、Oracle Database 12c新機能のオモテとウラ Vol.6 | アシスト
パフォーマンス改善と事前対策に役立つ Oracle SQLチューニング 本気で学ぶ実践的な考え方とテクニック
新・門外不出のOracle現場ワザ エキスパートが明かす運用・管理の極意

■おわりに
「上位頻度ヒストグラム」、「ハイブリッドヒストグラム」はどちらも未検証ですが、上位頻度ヒストグラムについてはヒストグラムの作成方法は頻度分布ヒストグラムと似ていますが、上位N個にフォーカスすることで頻度分布ヒストグラムより領域節約できる印象でした。
ハイブリッドヒストグラムについては「最初から頻度分布ヒストグラムで良いのでは?」と思いましたが、NDVが最大数(12cは2048)を超えた場合、頻度分布ヒストグラムで対応できないため、いったん高さ調整ヒストグラムヒストグラムを作成して最終的に頻度分布ヒストグラムを作成している印象でした。
なお、どのヒストグラムが作成されるかは参考資料(ヒストグラム)の「図11-1 ヒストグラム作成のディシジョン・ツリー」のフローで決まります。

Oracleの統計情報

今回はOracleの統計情報について検証しました。

統計情報(オプティマイザ統計とも呼ばれます)は、SQLの処理(ワークロード)の見積もりに必要なデータ(サーバやOS、データベースの特性を数値化したもの)です。
オプティマイザが実行計画を作成/選択する際、アクセスパスのコストを見積もる元データ(判断材料)として使用されます。

統計情報は2種類あります。

オブジェクト統計
表統計(XXX_TAB_STATISTICS、XXX_TABLES)
・行数(NUM_ROWS)
・ブロック数(BLOCKS)
・行の平均の長さ(AVG_ROW_LEN)
※XXX_TABLESは統計情報の失効状態(STALE_STATS)は確認不可
※ブロック数はHWMまでのブロック数
列統計(XXX_TAB_COL_STATISTICS)
・列内の個別値(NDV)の数(NUM_DISTINCT)
・列内のNULL数(NUM_NULLS)
ヒストグラム(HISTGRAM)
索引統計(XXX_IND_STATISTICS、XXX_INDEXES)
・索引の高さ(深さ)(BLEVEL)
・リーフブロック数(LEAF_BLOCKS)
クラスタ化係数(CLUSTERING_FACTOR)
※XXX_INDEXESは統計情報の失効状態(STALE_STATS)は確認不可

【補足】
NDV(Number of Distinct Value)
列に存在する値の種類の数
たとえば性別列に「男性」、「女性」のデータが存在する場合、2(性別列に存在するデータは2種類)となる

ヒストグラム
表の列データの分布状況
セレクティビティ(行の選択率)のコストを見積もるために使用される統計情報
通常の統計情報に比べ、より最適な実行計画を作成/選択するのが目的
頻度分布ヒストグラム、高さ調整ヒストグラム、上位頻度ヒストグラム、ハイブリッドヒストグラムがある
詳細は別記事で書きます
※セレクティビティ:行が選択される割合。全表スキャンか索引スキャンかの判断指標になる
※上位頻度ヒストグラム、ハイブリッドヒストグラムは12c以降の機能

クラスタ化係数
索引の列データと表の列データの分布度(データの格納順がどれくらい異なるかの指標)
索引の列データに格納されているデータと実際の表の列データに格納されているデータの順番がどれくらい揃っているかを表す
データの格納順がだいたい揃っている(一致している)場合、クラスタ化係数は小さくなる
データの格納順がほとんど揃っていない場合、クラスタ化係数は大きくなる
クラスタ化係数の値によってデータアクセスのオペレーションが変わってくる(クラスタ化係数が小さい場合、TABLE ACCESS BY INDEX ROWIDが採用されやすくなる)
詳細は別記事で書きます

システム統計
ハードウェアやOSのリソースI/O、使用率など(AUX_STATS$)
・CPUSPEEDNW(CPU速度(MHz))
・IOSEEKTIM(ディスクのシーク時間・回転待ち時間・OSオーバーヘッドの合計(ミリ秒))
・IOTFRSPEED(1ブロックの平均転送速度)

統計情報の収集方法は2種類あります。

・自動(自動メンテナンスタスク、動的サンプリング)
・手動(DBMS_STATSパッケージ、Enterprise Manager)

【補足】
自動メンテナンスタスク

データベースのメンテナンスのため、定期的に自動で実行されるタスク(ジョブ)
データベース作成時に自動的に作成される
自動メンテナンスタスクのうち、自動オプティマイザ統計収集ジョブが統計情報を収集する
※システム統計は収集されないため、手動で収集する必要がある
※自動メンテナンスタスクを使用する場合、STATISTICS_LEVEL初期化パラメータをTYPICALまたはALLに設定する
※自動メンテナンスタスクには自動セグメントアドバイザや自動SQLチューニングアドバイザ、SQL計画管理(SPM)展開アドバイザ(12c~)もある

収集時間帯(デフォルト)
・月~金 22:00~02:00
・土、日 06:00~20時間後に終了

収集対象
・オブジェクト統計が存在しないオブジェクト
・更新(表全体に対し、10%以上の変更)があったオブジェクト
・オブジェクト統計が古いオブジェクト

動的サンプリング
12c以降は動的統計と呼ばれている
オプティマイザが実行計画を作成する際、統計情報の欠落など統計情報が最新化されていない場合、自動的に統計情報を収集する
SQLを実行した際、何かしらの理由で統計情報が取得されていない場合、統計情報を取得してSQLの処理を継続するために動的サンプリングは実装されている
動的サンプリングを使用する場合、OPTIMIZER_DYNAMIC_SAMPLING初期化パラメータを設定する(0だと無効)

今回は手動による統計情報の収集とリストアを検証してみます。
Enterprise Manager環境が無いため、DBMS_STATSパッケージを使用します。

■検証環境
OS:Oracle Linux 6.5
DB/GI:Oracle Database 12c Release 1 (12.1.0.2.0) Enterprise Edition
※2ノードRAC(管理者管理型DB)

■前提
・非CDB
・索引の統計情報も自動収集する
select DBMS_STATS.GET_PREFS(pname => 'CASCADE') as cascade from dual;

CASCADE
------------------------------
DBMS_STATS.AUTO_CASCADE

■検証パターン
①統計情報取得&リストア(データベース単位)
②統計情報取得&リストア(ディクショナリ単位)
③統計情報取得&リストア(固定表(動的パフォーマンス表)単位)
④統計情報取得&リストア(スキーマ単位)
⑤統計情報取得&リストア(テーブル単位)
⑥統計情報取得&リストア(インデックス単位)
⑦統計情報取得&リストア(システム統計単位)

■検証
①統計情報取得&リストア(データベース単位)
オブジェクト統計のうち、データベース単位で統計情報を取得&リストアできるか検証します

【検証手順】
1. 統計情報取得状態確認
2. 統計情報取得
3. 統計情報取得状態確認
4. 統計情報リストア
5. 統計情報取得状態確認

【作業ログ】

1. 統計情報取得状態確認
SQL> SELECT OWNER, TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME IN('EMP', 'DEPT', 'BONUS', 'SALGRADE');

OWNER                TABLE_NAME        NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
-------------------- --------------- ---------- ---------- ----------- ------------------- ---------
SCOTT                BONUS
SCOTT                DEPT
SCOTT                EMP
SCOTT                SALGRADE

SQL> SELECT OWNER, INDEX_NAME, TABLE_NAME, BLEVEL, LEAF_BLOCKS, CLUSTERING_FACTOR, NUM_ROWS, LAST_ANALYZED, STALE_STATS FROM DBA_IND_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME IN('EMP', 'DEPT', 'BONUS', 'SALGRADE');

OWNER                INDEX_NAME      TABLE_NAME          BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR   NUM_ROWS LAST_ANALYZED       STALE_STA
-------------------- --------------- --------------- ---------- ----------- ----------------- ---------- ------------------- ---------
SCOTT                PK_DEPT         DEPT
SCOTT                PK_EMP          EMP

2. 統計情報取得
SQL> BEGIN
  2    DBMS_STATS.GATHER_DATABASE_STATS();
  3  END;
  4  /

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

3. 統計情報取得状態確認
SQL> SELECT OWNER, TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME IN('EMP', 'DEPT', 'BONUS', 'SALGRADE');

OWNER                TABLE_NAME        NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
-------------------- --------------- ---------- ---------- ----------- ------------------- ---------
SCOTT                BONUS                    0          0           0 2023-01-23 13:18:39 NO
SCOTT                DEPT                     4          5          20 2023-01-23 13:18:48 NO
SCOTT                EMP                     14          5          38 2023-01-23 13:18:50 NO
SCOTT                SALGRADE                 5          5          10 2023-01-23 13:19:27 NO

SQL> SELECT OWNER, INDEX_NAME, TABLE_NAME, BLEVEL, LEAF_BLOCKS, CLUSTERING_FACTOR, NUM_ROWS, LAST_ANALYZED, STALE_STATS FROM DBA_IND_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME IN('EMP', 'DEPT', 'BONUS', 'SALGRADE');

OWNER                INDEX_NAME      TABLE_NAME          BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR   NUM_ROWS LAST_ANALYZED       STALE_STA
-------------------- --------------- --------------- ---------- ----------- ----------------- ---------- ------------------- ---------
SCOTT                PK_DEPT         DEPT                     0           1                 1          4 2023-01-23 13:18:48 NO
SCOTT                PK_EMP          EMP                      0           1                 1         14 2023-01-23 13:18:50 NO
★統計情報が取得された

4. 統計情報リストア
SQL> --リストア前の統計情報(リストア確認用に事前に統計情報を取得済み)
SQL> SELECT OWNER, TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME IN('EMP', 'DEPT', 'BONUS', 'SALGRADE');

OWNER                TABLE_NAME        NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
-------------------- --------------- ---------- ---------- ----------- ------------------- ---------
SCOTT                BONUS                    0          0           0 2023-01-23 13:27:26 NO
SCOTT                DEPT                     4          5          20 2023-01-23 13:27:34 NO
SCOTT                EMP                     14          5          38 2023-01-23 13:27:36 NO
SCOTT                SALGRADE                 5          5          10 2023-01-23 13:28:11 NO

SQL> SELECT OWNER, INDEX_NAME, TABLE_NAME, BLEVEL, LEAF_BLOCKS, CLUSTERING_FACTOR, NUM_ROWS, LAST_ANALYZED, STALE_STATS FROM DBA_IND_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME IN('EMP', 'DEPT', 'BONUS', 'SALGRADE');

OWNER                INDEX_NAME      TABLE_NAME          BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR   NUM_ROWS LAST_ANALYZED       STALE_STA
-------------------- --------------- --------------- ---------- ----------- ----------------- ---------- ------------------- ---------
SCOTT                PK_DEPT         DEPT                     0           1                 1          4 2023-01-23 13:27:34 NO
SCOTT                PK_EMP          EMP                      0           1                 1         14 2023-01-23 13:27:36 NO

SQL> BEGIN
  2    DBMS_STATS.RESTORE_DATABASE_STATS(as_of_timestamp => TO_DATE('2023-01-23 13:19:28', 'YYYY-MM-DD HH24:MI:SS'));
  3  END;
  4  /

PL/SQLプロシージャが正常に完了しました。
★リストア日時は統計情報取得日時+1した値を指定します(取得時刻が2023-01-23 13:19:27の場合、2023-01-23 13:19:28にする)

5. 統計情報取得状態確認
SQL> SELECT OWNER, TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME IN('EMP', 'DEPT', 'BONUS', 'SALGRADE');

OWNER                TABLE_NAME        NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
-------------------- --------------- ---------- ---------- ----------- ------------------- ---------
SCOTT                BONUS                    0          0           0 2023-01-23 13:18:39 NO
SCOTT                DEPT                     4          5          20 2023-01-23 13:18:48 NO
SCOTT                EMP                     14          5          38 2023-01-23 13:18:50 NO
SCOTT                SALGRADE                 5          5          10 2023-01-23 13:19:27 NO

SQL> SELECT OWNER, INDEX_NAME, TABLE_NAME, BLEVEL, LEAF_BLOCKS, CLUSTERING_FACTOR, NUM_ROWS, LAST_ANALYZED, STALE_STATS FROM DBA_IND_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME IN('EMP', 'DEPT', 'BONUS', 'SALGRADE');

OWNER                INDEX_NAME      TABLE_NAME          BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR   NUM_ROWS LAST_ANALYZED       STALE_STA
-------------------- --------------- --------------- ---------- ----------- ----------------- ---------- ------------------- ---------
SCOTT                PK_DEPT         DEPT                     0           1                 1          4 2023-01-23 13:18:48 NO
SCOTT                PK_EMP          EMP                      0           1                 1         14 2023-01-23 13:18:50 NO
★統計情報がリストアされた

 

②統計情報取得&リストア(ディクショナリ単位)
オブジェクト統計のうち、ディクショナリ単位で統計情報を取得&リストアできるか検証します

【検証手順】
1. 統計情報取得状態確認
2. 統計情報取得
3. 統計情報取得状態確認
4. 統計情報リストア
5. 統計情報取得状態確認

【作業ログ】

1. 統計情報取得状態確認
SQL> SELECT TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE OWNER IN('SYS', 'SYSTEM', 'SCOTT') AND TABLE_NAME NOT LIKE 'X$%';

TABLE_NAME                       NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
------------------------------ ---------- ---------- ----------- ------------------- ---------
(略)
HISTGRM$                            47905       1374          34 2023-01-23 13:18:51 YES

2. 統計情報取得
SQL> BEGIN
  2    DBMS_STATS.GATHER_DICTIONARY_STATS();
  3  END;
  4  /

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

3. 統計情報取得状態確認
SQL> SELECT TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE OWNER IN('SYS', 'SYSTEM', 'SCOTT') AND TABLE_NAME NOT LIKE 'X$%';

TABLE_NAME                       NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
------------------------------ ---------- ---------- ----------- ------------------- ---------
(略)
HISTGRM$                            47862       1481          34 2023-01-23 13:45:18 NO
★統計情報が取得された

4. 統計情報リストア
SQL> --リストア前の統計情報(リストア確認用に事前に統計情報を取得済み)
SQL> SELECT TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE OWNER IN('SYS', 'SYSTEM', 'SCOTT');

TABLE_NAME                       NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
------------------------------ ---------- ---------- ----------- ------------------- ---------
(略)
HISTGRM$                            52592       1481          34 2023-01-23 13:52:25

SQL> BEGIN
  2    DBMS_STATS.RESTORE_DICTIONARY_STATS(as_of_timestamp => TO_DATE('2023-01-23 13:45:19', 'YYYY-MM-DD HH24:MI:SS'));
  3  END;
  4  /

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

5. 統計情報取得状態確認
SQL> SELECT TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE OWNER IN('SYS', 'SYSTEM', 'SCOTT') AND TABLE_NAME NOT LIKE 'X$%';

TABLE_NAME                       NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
------------------------------ ---------- ---------- ----------- ------------------- ---------
(略)
HISTGRM$                            47862       1481          34 2023-01-23 13:45:18 NO
★統計情報がリストアされた

 

③統計情報取得&リストア(固定表(動的パフォーマンス表)単位)
オブジェクト統計のうち、固定表(動的パフォーマンス表)単位で統計情報を取得&リストアできるか検証します

【検証手順】
1. 統計情報取得状態確認
2. 統計情報取得
3. 統計情報取得状態確認
4. 統計情報リストア
5. 統計情報取得状態確認

【作業ログ】

1. 統計情報取得状態確認
SQL> SELECT TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE TABLE_NAME LIKE 'X$%';

TABLE_NAME                       NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
------------------------------ ---------- ---------- ----------- ------------------- ---------
(略)
X$DIAG_INFO                            11                     63 2023-01-03 22:02:12

2. 統計情報取得
SQL> BEGIN
  2    DBMS_STATS.GATHER_FIXED_OBJECTS_STATS();
  3  END;
  4  /

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

3. 統計情報取得状態確認
SQL> SELECT TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE TABLE_NAME LIKE 'X$%';

TABLE_NAME                       NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
------------------------------ ---------- ---------- ----------- ------------------- ---------
(略)
X$DIAG_INFO                            11                     63 2023-01-23 14:30:32
★統計情報が取得された

4. 統計情報リストア
SQL> --リストア前の統計情報(リストア確認用に事前に統計情報を取得済み)
SQL> SELECT TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE TABLE_NAME LIKE 'X$%';

TABLE_NAME                       NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
------------------------------ ---------- ---------- ----------- ------------------- ---------
(略)
X$DIAG_INFO                            11                     63 2023-01-23 14:36:45

SQL> BEGIN
  2    DBMS_STATS.RESTORE_FIXED_OBJECTS_STATS(as_of_timestamp => TO_DATE('2023-01-23 14:30:33', 'YYYY-MM-DD HH24:MI:SS'));
  3  END;
  4  /

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

5. 統計情報取得状態確認
SQL> SELECT TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE TABLE_NAME LIKE 'X$%';

TABLE_NAME                       NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
------------------------------ ---------- ---------- ----------- ------------------- ---------
(略)
X$DIAG_INFO                            11                     63 2023-01-23 14:30:32
★統計情報がリストアされた

 

④統計情報取得&リストア(スキーマ単位)
オブジェクト統計のうち、スキーマ単位で統計情報を取得&リストアできるか検証します

【検証手順】
1. 統計情報取得状態確認
2. 統計情報取得
3. 統計情報取得状態確認
4. 統計情報リストア
5. 統計情報取得状態確認

【作業ログ】

1. 統計情報取得状態確認
SQL> SELECT OWNER, TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME IN('EMP', 'DEPT', 'BONUS', 'SALGRADE');

OWNER                TABLE_NAME        NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
-------------------- --------------- ---------- ---------- ----------- ------------------- ---------
SCOTT                BONUS                    0          0           0 2023-01-23 13:18:39 NO
SCOTT                DEPT                     4          5          20 2023-01-23 13:18:48 NO
SCOTT                EMP                     14          5          38 2023-01-23 13:18:50 NO
SCOTT                SALGRADE                 5          5          10 2023-01-23 13:19:27 NO

SQL> SELECT OWNER, INDEX_NAME, TABLE_NAME, BLEVEL, LEAF_BLOCKS, CLUSTERING_FACTOR, NUM_ROWS, LAST_ANALYZED, STALE_STATS FROM DBA_IND_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME IN('EMP', 'DEPT', 'BONUS', 'SALGRADE');

OWNER                INDEX_NAME      TABLE_NAME          BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR   NUM_ROWS LAST_ANALYZED       STALE_STA
-------------------- --------------- --------------- ---------- ----------- ----------------- ---------- ------------------- ---------
SCOTT                PK_DEPT         DEPT                     0           1                 1          4 2023-01-23 13:18:48 NO
SCOTT                PK_EMP          EMP                      0           1                 1         14 2023-01-23 13:18:50 NO

2. 統計情報取得
SQL> BEGIN
  2    DBMS_STATS.GATHER_SCHEMA_STATS(ownname => 'SCOTT');
  3  END;
  4  /

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

3. 統計情報取得状態確認
SQL> SELECT OWNER, TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME IN('EMP', 'DEPT', 'BONUS', 'SALGRADE');

OWNER                TABLE_NAME        NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
-------------------- --------------- ---------- ---------- ----------- ------------------- ---------
SCOTT                BONUS                    0          0           0 2023-01-23 14:50:17 NO
SCOTT                DEPT                     4          5          20 2023-01-23 14:50:17 NO
SCOTT                EMP                     14          5          38 2023-01-23 14:50:17 NO
SCOTT                SALGRADE                 5          5          10 2023-01-23 14:50:17 NO

SQL> SELECT OWNER, INDEX_NAME, TABLE_NAME, BLEVEL, LEAF_BLOCKS, CLUSTERING_FACTOR, NUM_ROWS, LAST_ANALYZED, STALE_STATS FROM DBA_IND_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME IN('EMP', 'DEPT', 'BONUS', 'SALGRADE');

OWNER                INDEX_NAME      TABLE_NAME          BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR   NUM_ROWS LAST_ANALYZED       STALE_STA
-------------------- --------------- --------------- ---------- ----------- ----------------- ---------- ------------------- ---------
SCOTT                PK_DEPT         DEPT                     0           1                 1          4 2023-01-23 14:50:17 NO
SCOTT                PK_EMP          EMP                      0           1                 1         14 2023-01-23 14:50:17 NO
★統計情報が取得された

4. 統計情報リストア
SQL> --リストア前の統計情報(リストア確認用に事前に統計情報を取得済み)
SQL> SELECT OWNER, TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME IN('EMP', 'DEPT', 'BONUS', 'SALGRADE');

OWNER                TABLE_NAME        NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
-------------------- --------------- ---------- ---------- ----------- ------------------- ---------
SCOTT                BONUS                    0          0           0 2023-01-23 14:58:28 NO
SCOTT                DEPT                     4          5          20 2023-01-23 14:58:28 NO
SCOTT                EMP                     14          5          38 2023-01-23 14:58:28 NO
SCOTT                SALGRADE                 5          5          10 2023-01-23 14:58:28 NO

SQL> SELECT OWNER, INDEX_NAME, TABLE_NAME, BLEVEL, LEAF_BLOCKS, CLUSTERING_FACTOR, NUM_ROWS, LAST_ANALYZED, STALE_STATS FROM DBA_IND_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME IN('EMP', 'DEPT', 'BONUS', 'SALGRADE');

OWNER                INDEX_NAME      TABLE_NAME          BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR   NUM_ROWS LAST_ANALYZED       STALE_STA
-------------------- --------------- --------------- ---------- ----------- ----------------- ---------- ------------------- ---------
SCOTT                PK_DEPT         DEPT                     0           1                 1          4 2023-01-23 14:58:28 NO
SCOTT                PK_EMP          EMP                      0           1                 1         14 2023-01-23 14:58:28 NO

SQL> BEGIN
  2    DBMS_STATS.RESTORE_SCHEMA_STATS(ownname         => 'SCOTT',
  3                                    as_of_timestamp => TO_DATE('2023-01-23 14:50:18', 'YYYY-MM-DD HH24:MI:SS'));
  4  END;
  5  /

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

5. 統計情報取得状態確認
SQL> SELECT OWNER, TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME IN('EMP', 'DEPT', 'BONUS', 'SALGRADE');

OWNER                TABLE_NAME        NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
-------------------- --------------- ---------- ---------- ----------- ------------------- ---------
SCOTT                BONUS                    0          0           0 2023-01-23 14:50:17 NO
SCOTT                DEPT                     4          5          20 2023-01-23 14:50:17 NO
SCOTT                EMP                     14          5          38 2023-01-23 14:50:17 NO
SCOTT                SALGRADE                 5          5          10 2023-01-23 14:50:17 NO

SQL> SELECT OWNER, INDEX_NAME, TABLE_NAME, BLEVEL, LEAF_BLOCKS, CLUSTERING_FACTOR, NUM_ROWS, LAST_ANALYZED, STALE_STATS FROM DBA_IND_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME IN('EMP', 'DEPT', 'BONUS', 'SALGRADE');

OWNER                INDEX_NAME      TABLE_NAME          BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR   NUM_ROWS LAST_ANALYZED       STALE_STA
-------------------- --------------- --------------- ---------- ----------- ----------------- ---------- ------------------- ---------
SCOTT                PK_DEPT         DEPT                     0           1                 1          4 2023-01-23 14:50:17 NO
SCOTT                PK_EMP          EMP                      0           1                 1         14 2023-01-23 14:50:17 NO
★統計情報がリストアされた

 

⑤統計情報取得&リストア(テーブル単位)
オブジェクト統計のうち、テーブル単位で統計情報を取得&リストアできるか検証します

【検証手順】
1. 統計情報取得状態確認
2. 統計情報取得
3. 統計情報取得状態確認
4. 統計情報リストア
5. 統計情報取得状態確認

【作業ログ】

1. 統計情報取得状態確認
SQL> SELECT OWNER, TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME = 'EMP';

OWNER                TABLE_NAME        NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
-------------------- --------------- ---------- ---------- ----------- ------------------- ---------
SCOTT                EMP                     14          5          38 2023-01-23 14:50:17 NO

2. 統計情報取得
SQL> BEGIN
  2    DBMS_STATS.GATHER_TABLE_STATS(ownname => 'SCOTT',
  3                                  tabname => 'EMP');
  4  END;
  5  /

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

3. 統計情報取得状態確認
SQL> SELECT OWNER, TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME = 'EMP';

OWNER                TABLE_NAME        NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
-------------------- --------------- ---------- ---------- ----------- ------------------- ---------
SCOTT                EMP                     14          5          38 2023-01-23 15:12:51 NO
★統計情報が取得された

4. 統計情報リストア
SQL> --リストア前の統計情報(リストア確認用に事前に統計情報を取得済み)
SQL> SELECT OWNER, TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME = 'EMP';

OWNER                TABLE_NAME        NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
-------------------- --------------- ---------- ---------- ----------- ------------------- ---------
SCOTT                EMP                     14          5          38 2023-01-23 15:17:27 NO

SQL> BEGIN
  2    DBMS_STATS.RESTORE_TABLE_STATS(ownname         => 'SCOTT',
  3                                   tabname         => 'EMP',
  4                                   as_of_timestamp => TO_DATE('2023-01-23 15:12:52', 'YYYY-MM-DD HH24:MI:SS'));
  5  END;
  6  /

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

5. 統計情報取得状態確認
SQL> SELECT OWNER, TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME = 'EMP';

OWNER                TABLE_NAME        NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
-------------------- --------------- ---------- ---------- ----------- ------------------- ---------
SCOTT                EMP                     14          5          38 2023-01-23 15:12:51 NO
★統計情報がリストアされた

 

⑥統計情報取得&リストア(インデックス単位)
オブジェクト統計のうち、インデックス単位で統計情報を取得&リストアできるか検証します

【検証手順】
1. 統計情報取得状態確認
2. 統計情報取得
3. 統計情報取得状態確認
4. 統計情報リストア
5. 統計情報取得状態確認

【作業ログ】

1. 統計情報取得状態確認
SQL> SELECT OWNER, INDEX_NAME, TABLE_NAME, BLEVEL, LEAF_BLOCKS, CLUSTERING_FACTOR, NUM_ROWS, LAST_ANALYZED, STALE_STATS FROM DBA_IND_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME = 'EMP';

OWNER                INDEX_NAME      TABLE_NAME          BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR   NUM_ROWS LAST_ANALYZED       STALE_STA
-------------------- --------------- --------------- ---------- ----------- ----------------- ---------- ------------------- ---------
SCOTT                PK_EMP          EMP                      0           1                 1         14 2023-01-23 15:12:51 NO

2. 統計情報取得
SQL> BEGIN
  2    DBMS_STATS.GATHER_INDEX_STATS(ownname => 'SCOTT',
  3                                  indname => 'PK_EMP');
  4  END;
  5  /

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

3. 統計情報取得状態確認
SQL> SELECT OWNER, INDEX_NAME, TABLE_NAME, BLEVEL, LEAF_BLOCKS, CLUSTERING_FACTOR, NUM_ROWS, LAST_ANALYZED, STALE_STATS FROM DBA_IND_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME = 'EMP';

OWNER                INDEX_NAME      TABLE_NAME          BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR   NUM_ROWS LAST_ANALYZED       STALE_STA
-------------------- --------------- --------------- ---------- ----------- ----------------- ---------- ------------------- ---------
SCOTT                PK_EMP          EMP                      0           1                 1         14 2023-01-23 15:25:30 NO
★統計情報が取得された

4. 統計情報リストア
★インデックスの統計情報のリストア関数が無いため確認不可

 

⑦統計情報取得&リストア(システム統計単位)
システム統計の統計情報を取得&リストアできるか検証します

【検証手順】
1. 統計情報取得状態確認
2. 統計情報取得
3. 統計情報取得状態確認
4. 統計情報リストア
5. 統計情報取得状態確認

【作業ログ】

1. 統計情報取得状態確認
SQL> SELECT * FROM AUX_STATS$;

SNAME                          PNAME                               PVAL1 PVAL2
------------------------------ ------------------------------ ---------- --------------------
SYSSTATS_INFO                  STATUS                                    COMPLETED
SYSSTATS_INFO                  DSTART                                    07-07-2014 06:53
SYSSTATS_INFO                  DSTOP                                     07-07-2014 06:53
SYSSTATS_INFO                  FLAGS                                   1
SYSSTATS_MAIN                  CPUSPEEDNW                      3308.9701
SYSSTATS_MAIN                  IOSEEKTIM                              10
SYSSTATS_MAIN                  IOTFRSPEED                           4096
SYSSTATS_MAIN                  SREADTIM
SYSSTATS_MAIN                  MREADTIM
SYSSTATS_MAIN                  CPUSPEED
SYSSTATS_MAIN                  MBRC
SYSSTATS_MAIN                  MAXTHR
SYSSTATS_MAIN                  SLAVETHR

2. 統計情報取得
SQL> BEGIN
  2    DBMS_STATS.GATHER_SYSTEM_STATS();
  3  END;
  4  /

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

3. 統計情報取得状態確認
SQL> SELECT * FROM AUX_STATS$;

SNAME                          PNAME                               PVAL1 PVAL2
------------------------------ ------------------------------ ---------- --------------------
SYSSTATS_INFO                  STATUS                                    COMPLETED
SYSSTATS_INFO                  DSTART                                    01-23-2023 15:42
SYSSTATS_INFO                  DSTOP                                     01-23-2023 15:42
SYSSTATS_INFO                  FLAGS                                   1
SYSSTATS_MAIN                  CPUSPEEDNW                           1477
SYSSTATS_MAIN                  IOSEEKTIM                              10
SYSSTATS_MAIN                  IOTFRSPEED                           4096
SYSSTATS_MAIN                  SREADTIM
SYSSTATS_MAIN                  MREADTIM
SYSSTATS_MAIN                  CPUSPEED
SYSSTATS_MAIN                  MBRC
SYSSTATS_MAIN                  MAXTHR
SYSSTATS_MAIN                  SLAVETHR
★統計情報が取得された

4. 統計情報リストア
SQL> --リストア前の統計情報(リストア確認用に事前に統計情報を取得済み)
SQL> SELECT * FROM AUX_STATS$;

SNAME                          PNAME                               PVAL1 PVAL2
------------------------------ ------------------------------ ---------- --------------------
SYSSTATS_INFO                  STATUS                                    COMPLETED
SYSSTATS_INFO                  DSTART                                    01-23-2023 15:46
SYSSTATS_INFO                  DSTOP                                     01-23-2023 15:46
SYSSTATS_INFO                  FLAGS                                   1
SYSSTATS_MAIN                  CPUSPEEDNW                           1477
SYSSTATS_MAIN                  IOSEEKTIM                              10
SYSSTATS_MAIN                  IOTFRSPEED                           4096
SYSSTATS_MAIN                  SREADTIM
SYSSTATS_MAIN                  MREADTIM
SYSSTATS_MAIN                  CPUSPEED
SYSSTATS_MAIN                  MBRC
SYSSTATS_MAIN                  MAXTHR
SYSSTATS_MAIN                  SLAVETHR

SQL> BEGIN
  2    DBMS_STATS.RESTORE_SYSTEM_STATS(as_of_timestamp => TO_DATE('23-01-23 15:42:55', 'YY-MM-DD HH24:MI:SS'));
  3  END;
  4  /

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

5. 統計情報取得状態確認
SQL> SELECT * FROM AUX_STATS$;

SNAME                          PNAME                               PVAL1 PVAL2
------------------------------ ------------------------------ ---------- --------------------
SYSSTATS_INFO                  STATUS                                    COMPLETED
SYSSTATS_INFO                  DSTART                                    01-23-2023 15:51
SYSSTATS_INFO                  DSTOP                                     01-23-2023 15:51
SYSSTATS_INFO                  FLAGS                                 128
SYSSTATS_MAIN                  CPUSPEEDNW                           1477
SYSSTATS_MAIN                  IOSEEKTIM                              10
SYSSTATS_MAIN                  IOTFRSPEED                           4096
SYSSTATS_MAIN                  SREADTIM
SYSSTATS_MAIN                  MREADTIM
SYSSTATS_MAIN                  CPUSPEED
SYSSTATS_MAIN                  MBRC
SYSSTATS_MAIN                  MAXTHR
SYSSTATS_MAIN                  SLAVETHR
★リストアは正常終了したが、統計情報取得日時は指定した時刻に戻らなかった(リストアを実施した日時になった)

 

■参考資料
DBMS_STATS
自動データベース・メンテナンス・タスクの管理
https://www.oracle.com/technetwork/jp/ondemand/branch/120328-cbo-1593994-ja.pdf
パフォーマンス改善と事前対策に役立つ Oracle SQLチューニング 本気で学ぶ実践的な考え方とテクニック

■おわりに
RESTORE_XXX_STATS()関数に指定するリストア日時に「統計情報を取得した日時」を指定した場合、NUM_ROWS列などの統計情報がNULLになってしまいました。
リストア日時には統計情報を取得した日時より、1大きい値を指定する必要がありそうでした(秒なら1秒進める、分なら1分進める、時間なら1時間進める)

SQL> --統計情報取得
SQL> SELECT OWNER, TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME = 'EMP';

OWNER                TABLE_NAME        NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
-------------------- --------------- ---------- ---------- ----------- ------------------- ---------
SCOTT                EMP                     14          5          38 2023-01-23 13:18:50 NO

SQL> --リストア前の統計情報(リストア確認用に事前に統計情報を取得済み)
SQL> SELECT OWNER, TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME = 'EMP';

OWNER                TABLE_NAME        NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
-------------------- --------------- ---------- ---------- ----------- ------------------- ---------
SCOTT                EMP                     14          5          38 2023-01-23 13:27:36 NO

SQL> --リストア日時に「統計情報を取得した日時」を指定してリストア
SQL> BEGIN
  2    DBMS_STATS.RESTORE_TABLE_STATS(ownname         => 'SCOTT',
  3                                   tabname         => 'EMP',
  4                                   as_of_timestamp => TO_DATE('2023-01-23 13:18:50', 'YYYY-MM-DD HH24:MI:SS'));
  5  END;
  6  /

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

SQL> SELECT OWNER, TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME = 'EMP';

OWNER                TABLE_NAME        NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
-------------------- --------------- ---------- ---------- ----------- ------------------- ---------
SCOTT                EMP
★統計情報がNULLになってしまった

SQL> --リストア日時に「統計情報を取得した日時+1」を指定してリストア
SQL> BEGIN
  2    DBMS_STATS.RESTORE_TABLE_STATS(ownname         => 'SCOTT',
  3                                   tabname         => 'EMP',
  4                                   as_of_timestamp => TO_DATE('2023-01-23 13:18:51', 'YYYY-MM-DD HH24:MI:SS'));
  5  END;
  6  /

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

SQL> SELECT OWNER, TABLE_NAME, NUM_ROWS, BLOCKS, AVG_ROW_LEN, LAST_ANALYZED, STALE_STATS FROM DBA_TAB_STATISTICS WHERE OWNER = 'SCOTT' AND TABLE_NAME = 'EMP';

OWNER                TABLE_NAME        NUM_ROWS     BLOCKS AVG_ROW_LEN LAST_ANALYZED       STALE_STA
-------------------- --------------- ---------- ---------- ----------- ------------------- ---------
SCOTT                EMP                     14          5          38 2023-01-23 13:18:50 NO
★「統計情報を取得した日時」の統計情報が入った(リストアされた)

 

なお、リストアに使用する統計情報はデフォルトで31日間保存されます。
統計情報は他にも保留(取得した統計情報をオプティマイザに公開しない)、エクスポート/インポート(EXPORT_XXX_STATS/IMPORT_XXX_STATS)、ロック(統計情報を変更させない)などができます。

sqlplusの/nologオプション

今さらですがsqlplusで/nologオプションを指定するメリットを知ったので軽く検証しました。

/nologオプション
SQL*Plusを起動する際、データベースに接続しない

メリット
一例として、データベースに接続せずにSQL*Plusを起動するため、UNIX系OSでpsやhistoryを実行した場合、DBユーザ名やパスワードが表示されない
製品バージョンによるかもしれませんが、SQL*Plus起動時にDBユーザ名やパスワードを指定した状態でダイレクトにDB接続すると、DBユーザやパスワードがpsで見れてしまうケースがあるそうです。(sqlplusプロセスの引数の情報もそのまま表示してしまうため)
historyにもコマンド履歴として残ります。
セキュリティ観点からパスワードが見れる(残ってしまう)のは好ましくありません。
SQL*Plus起動時に/nologオプションを指定して(sqlplus /nolog)、SQL*Plus起動後にDB接続(connect)すれば、psやhistoryでも「sqlplus /nolog」という情報しか残らなくなるため、パスワードが見れる(残る)という事象を防げます。

■検証環境
OS:Oracle Linux 6.5
DB/GI:Oracle Database 12c Release 1 (12.1.0.2.0) Enterprise Edition
※2ノードRAC(管理者管理型DB)

■前提
・非CDB

■検証パターン
①/nologオプションなしでDB接続
②/nologオプションありでDB接続

■検証
①/nologオプションなしでDB接続
/nologオプションを指定せず、SQL*Plusを起動してDB接続します
DB接続後、別セッションでpsコマンドでsqlplusプロセスの状態も確認します

【検証手順】
1. DBユーザおよびパスワードを指定してSQL*Plus起動(DB接続)
2. sqlplusプロセス確認

【作業ログ】

1. DBユーザおよびパスワードを指定してSQL*Plus起動(DB接続)
[oracle@node1 ~]$ sqlplus system/password
SQL> show user
ユーザーは"SYSTEM"です。
SQL> set lines 200 pages 3000
SQL> set tab off
SQL> select instance_name, status from v$instance;

INSTANCE_NAME                                    STATUS
------------------------------------------------ ------------------------------------
orcl1                                            OPEN

2. sqlplusプロセス状態確認
SQL> !ps -ef | grep sqlplus | grep -v grep
oracle    4514  4425  0 12:44 pts/0    00:00:00 sqlplus                
★psではDBユーザ名とパスワードは見えなかった
 予想だがsqlplusプロセス名の右に15文字分の半角スペースがあるので、「system/password」という文字列をマスキングしているのかもしれない

[oracle@node1 ~]$ history | grep sqlplus | grep -v grep
 1002  sqlplus system/password
historyにはDBユーザ名とパスワードが残っている

 

②/nologオプションありでDB接続
/nologオプションを指定して、SQL*Plusを起動してDB接続します
SQL*Plus起動後・DB接続後、別セッションでpsコマンドでsqlplusプロセスの状態も確認します

【検証手順】
1. /nologを指定してSQL*Plus起動
2. sqlplusプロセス状態確認
3. DBユーザおよびパスワードを指定してDB接続
4. sqlplusプロセス状態確認

【作業ログ】

1. /nologを指定してSQL*Plus起動
[oracle@node1 ~]$ sqlplus /nolog
SQL> show user
ユーザーは""です。
SQL> select instance_name, status from v$instance;
SP2-0640: 接続されていません。

2. sqlplusプロセス状態確認
SQL> !ps -ef | grep sqlplus | grep -v grep
oracle    6857  4425  0 12:50 pts/0    00:00:00 sqlplus       
★今度はsqlplusプロセス名の右に6文字分の半角スペースがあるので、「/nolog」という文字列をマスキングしているのかもしれない

[oracle@node1 ~]$ history | grep sqlplus | grep -v grep
 1008  sqlplus /nolog
historyには「sqlplus /nolog」という文字列だけ残っている

3. DBユーザおよびパスワードを指定してDB接続
[oracle@node1 ~]$ sqlplus /nolog
SQL> connect system/password
接続されました。
SQL> show user
ユーザーは"SYSTEM"です。
SQL> select instance_name, status from v$instance;

INSTANCE_NAME                                    STATUS
------------------------------------------------ ------------------------------------
orcl1                                            OPEN

4. sqlplusプロセス状態確認
SQL> !ps -ef | grep sqlplus | grep -v grep
oracle    7680  4425  0 12:53 pts/0    00:00:00 sqlplus       
★psではDBユーザ名とパスワードは見えなかった
 今回もsqlplusプロセス名の右に6文字分の半角スペースがある

[oracle@node1 ~]$ history | grep sqlplus | grep -v grep
 1011  sqlplus /nolog
historyには「sqlplus /nolog」という文字列だけ残っている

 

■参考資料
プログラム引数のセキュリティについて

■おわりに
検証した製品のバージョンによるかもしれませんが、12cR1ではsqlplusでDBユーザ名とパスワードを指定しても、psではDBユーザ名とパスワードは表示されませんでした。(スペースに置き換わってるように見えました)
特定の製品バージョンからsqlplusの引数をマスキングするようになったのかもしれません。
ただ、historyには残ってしまうのでセキュリティのことを考慮するなら/nologオプションでSQL*Plus起動後、sqlplus内でDB接続した方が安全だと思います。

なお、rootユーザからoracleユーザへスイッチと同時にDB接続したら、その情報がpsでも見えました。

su - oracle -c "sqlplus system/password"

[root@node1 ~]# su - oracle -c "sqlplus system/password"
SQL> show user
ユーザーは"SYSTEM"です。
SQL> select instance_name, status from v$instance;

INSTANCE_NAME                                    STATUS
------------------------------------------------ ------------------------------------
orcl1                                            OPEN

SQL> !ps -ef | grep sqlplus | grep -v grep
root      8758 11549  0 12:55 pts/0    00:00:00 su - oracle -c sqlplus system/password
oracle    8759  8758  0 12:55 ?        00:00:00 sqlplus                

[root@node1 ~]# history | grep sqlplus | grep -v grep
 1025  su - oracle -c "sqlplus system/password"

 

Oracleの読取り一貫性

今回はOracleの読取り一貫性について調査しました。

読取り一貫性とは、特定のトランザクションがSELECT文(データ読込み)を開始した時点で確定しているデータを読み込ませる機能です。
いったんOracleのSELECT文(データ読込み)の動きを復習します。

SELECT中に別トランザクションでデータ更新された場合、このUNDOセグメントのデータを使用して、読取り一貫性を実現しています。

では、SELECT中にUNDOセグメントがいっぱいになって読込み開始時点のデータが上書きされてしまった場合、どうなってしまうのでしょうか?
結論から言うと「ORA-01555: スナップショットが古すぎます」が発生します。
読込み開始時点のUNDOデータが上書きされて、読取り一貫性を実現できなくなってしまったためです。

■参考資料
ORACLE MASTER Silver[Silver DBA11g](試験番号:1Z0-052)完全詳解+精選問題集

■おわりに
個人の感想ですが、データベースの運用担当をしていた頃は「ORA-01555」のエラーをよく見ました。
UNDO_RETENTION値を増やせばその分「ORA-01555」エラーが減るケースが多いと思いますが、UNDO表領域が物理的にサイズ不足になると今度は「ORA-30036: string(UNDO表領域'string'内)でセグメントを拡張できません」エラーが発生してしまいます。