Oracle DB:実行計画に影響を与える者たち

Oracle DBのバージョンを変えたことがある方は、実行計画が急に変わって困ったことが１度はあると思います。
CBO(Cost Base Optimazer)で実行計画に影響を与える機能をまとめてみました。
内容は薄いので詳細は各機能を個別に調査してみてください。

## 基本:SQLの実行の流れ
1. SQL解析(Parse)
   1.1. 構文チェック
　SQL形式チェック

   1.2. セマンティクス・チェック
　指定した表やカラムなどのオブジェクトが存在するか

   1.3. 共有プール・チェック
　共有プールのV$SQLにアクセスし、
　実行するSQLがすでに解析済(実行計画が存在する)かチェックをする。
　既に解析済であれば、ソフトパース(実行計画の再利用)を行い、
　そうでなければハードパースを行う。

2. オプティマイズ
　SQLをハードパースし、コストベースで最適な実行計画を作成する。
　解析された実行計画は共有プールのV$SQLに保存される。

3. SQLの実行

## 統計情報
ハードパース時作成される実行計画のインプットとなる統計情報は以下のようなものがある。
- 表統計(DBA_TAB_STATISTICS)：行数、ブロック数、平均行長
- 列統計(DBA_TAB_COL_STATISTICS)：列値の種類、NULLの数、データ分布
- 索引統計(DBA_IND_STATISTICS)：リーフ・ブロック数、階層数、クラスタ化係数
- システム統計(AUX_STATS$)：CPUパフォーマンスと使用率、I/Oパフォーマンスと使用率

統計情報の収集方法は以下の6つ。
- 手動収集
  DBMS_STATS.GATHER_TABLE_STATSによる手動収集する。
- 自動収集
  スケジュールに従い自動収集する。dba_scheduler_windowsでスケジュールを確認できる。
- 自動オプティマイザ自動収集(旧称：動的サンプリング)
  統計情報がない、古い場合にハードパース時に動的統計を取得する。
  OPTIMIZER_DYNAMIC_SAMPLINGで動的統計を収集するタイミングと、サンプル・サイズの両方を設定できる
- バルク・ロードのためのオンライン統計収集(Online statistics gathering for bulk loads)\
12.1.0.1から有効
バルク・ロード（CREATE TABLE AS SELECTやダイレクト・パス・インサート）時に統計情報を取得する。空表の場合は統計情報は取得されない。
[Oracle Help Center:バルク・ロードのためのオンライン統計収集](https://docs.oracle.com/cd/F19136_01/tgsql/optimizer-statistics-concepts.html#GUID-E920301F-FFC4-4AB7-8DEB-11BA56088E0B)

- 高頻度自動オプティマイザ統計収集
  19cから利用可能。
  高頻度で失効した統計情報の再取得を自動化できる。
DBMS_STATS.SET_GLOBAL_PREFSパラメータでタスクの設定を行う。
AUTO_TASK_STATUS:機能のON/OFF（デフォルトはOFF）
AUTO_TASK_MAX_RUN_TIME:最大実行時間。超えたら収集は打ち切り。（デフォルトは3600秒
AUTO_TASK_INTERVAL:収集処理のインターバル（デフォルトは900秒）
[高頻度自動オプティマイザ統計収集](https://bismarc256.hateblo.jp/entry/2020/05/28/200000)
[Oracle Database 19cにおけるオプティマイザ 統計収集の ベスト・プラクティス@PDF](https://www.oracle.com/technetwork/jp/database/bi-datawarehousing/twp-bp-for-stats-gather-19c-5324205-ja.pdf)

- リアルタイム統計情報
  19cから利用可能。insert, update, merge時に自動で収集。
  他の統計情報とは別で管理されており、以下の設定で収集と利用を設定できる。
リアルタイム統計を使用するか制御するパラメータ
設定値 : _optimizer_use_stats_on_conventional_dml
リアルタイム統計を収集するか制御するパラメータ
設定値 : _optimizer_gather_stats_on_conventional_dml
[Oracle Help Center : SQLチューニング・ガイド](https://docs.oracle.com/cd/E82638_01/tgsql/gathering-optimizer-statistics.html#GUID-245F23B2-24AF-44D8-9F12-99FD1215E878)
[おらくるのいる生活](https://bismarc256.hateblo.jp/entry/2020/05/22/200000)
[特定のテーブルのみのリアルタイム統計オラクル19cについて](https://isolution.pro/q/db13265172/tokutei-no-te-buru-nomi-no-riarutaimu-tokei-orakuru-19-c-nitsuite)
[Oracle Database 19cにおけるオプティマイザ 統計収集の ベスト・プラクティス@PDF](https://www.oracle.com/technetwork/jp/database/bi-datawarehousing/twp-bp-for-stats-gather-19c-5324205-ja.pdf)

:::note warn
統計情報（表統計、索引統計）は１０％を超えるレコード更新がされると無効（失効）される。この状態でハードパースが行われると性能障害になるリスクが高まります。
大量にデータが更新される処理のあとには、統計情報を再取得することを検討してください。
:::

## 最適な実行計画を目指す機能たち
 基本は統計情報を元に実行計画を作成しますが、実データと乖離のある統計情報による不適切な実行計画が作成されないようにOracleには便利な機能(時に問題児)があります。

### バインド変数系
バインド変数化されたSQLは範囲条件の場合は5%のデータがあるという判定が行われindexを採用する実行計画になることが多いらしいです。(情報が古いので19cではわかりません。)
[門外不出のOracle現場ワザ](https://www.oracle.com/jp/technical-resources/articles/unexpected-oracle-field/chapter4.html)

バインド変数化されたSQLに対して、更によい実行計画を形成できるような機能があります。

- バインドピーク機能(Bind Peek)
バインド変数に値を入れた状態でハードパースする。
バインド変数の値によって取得件数が大きく異なる場合、SQL実行が遅くなるリスクがある。
設定 : _OPTIM_PEEK_USER_BINDS

- 適応カーソル共有(Adaptive Cursor Sharing)
  バインドピークの問題を緩和するための機能。
  バインド変数に設定された値を監視し、必要に応じて新しい実行計画を追加作成する
設定 : _OPTIMIZER_ADAPTIVE_CURSOR_SHARING

- 動的統計の拡張
11.2.0.4から有効
OPTIMIZER_DYNAMIC_SAMPLINGに"11"が追加。11のとき、動的統計を収集するかどうかをOracleが自動的に決定します。
[Oracle® Databaseパフォーマンス・チューニング・ガイド
11gリリース2 (11.2)](https://docs.oracle.com/cd/E16338_01/server.112/b56312/whatsnew.htm)


- 適応問い合わせ最適化(Adaptive Query Optimization)
適応計画と自動再最適化の機能
12.1.0.1から有効
<B>適応計画</B>
ハードパース時に対象表の統計情報の一部（行数等）を取得し、統計情報と乖離があれば統計情報に基づいた実行計画からサンプリングから得た実行計画に切り替える。
設定値：OPTIMIZER_ADAPTIVE_PLANS
<B>自動再最適化</B>
SQL の実行時に収集された統計情報が乖離している場合、次のソフトパース時に新しく収集された統計情報を使用して実行計画を作成
後に説明するカーディナリティ・フィードバックの機能も内包している。
設定値：OPTIMIZER_ADAPTIVE_STATISTICS
[徹底解説！Oracle Database 12cのすべて Vol.5](https://www.ashisuto.co.jp/tech-[津島博士のパフォーマンス講座　第57回　適応問合せ最適化について](https://blogs.oracle.com/otnjp/post/tsushima-hakushi-57)note/article/20131031_oracle.html)

- カーディナリティ・フィードバック(Cardinality Feedback)
11.2.0.1から有効
実行計画を生成するオプティマイザのインプット情報に、実データのカーディナリティ（データの種類の多さ）情報を加えることによって、より最適な実行計画を生成する機能。
設定値：_optimizer_use_feedback 
[Output Place : Cardinality Feedback（カーディナリティ　フィードバック)とは](http://output-place.blogspot.com/2013/05/cardinality-feedback.html)


### SQL編集関係
ハードパース時により早く実行可能なSQLに自動変換してくれる機能たち
これらは統計情報と実データの乖離には依存せず、単純にSQLチューニングしてくれるイメージ。

- Simple View Merging
[GAGA LIFE. : Oracle 問合せ変換②-1 ビュー・マージ(Simple View Merging)](https://www.undercoverlog.com/entry/2018/06/23/Oracle_%E5%95%8F%E5%90%88%E3%81%9B%E5%A4%89%E6%8F%9B%E2%91%A1-1_%E3%83%93%E3%83%A5%E3%83%BC%E3%83%BB%E3%83%9E%E3%83%BC%E3%82%B8%28Simple_View_Merging%29)

- Complex View Merging
[GAGA LIFE. : Oracle 問合せ変換②-2 ビュー・マージ(Complex View Merging)](https://www.undercoverlog.com/entry/2018/09/29/Oracle_%E5%95%8F%E5%90%88%E3%81%9B%E5%A4%89%E6%8F%9B%E2%91%A1-2_%E3%83%93%E3%83%A5%E3%83%BC%E3%83%BB%E3%83%9E%E3%83%BC%E3%82%B8%28Complex_View_Merging%29)

- OR拡張(Cost-based OR expansion)
12.2.0.1から有効
[Oracle Help Center SQLチューニング・ガイド: OR拡張](https://docs.oracle.com/cd/F19136_01/tgsql/query-transformations.html#GUID-0D5B9093-CDEA-45AC-A607-1F0D8F2615DD)

- 結合の因数分解(Join Factorization)
11.2.0.1から有効
[Oracle Help Center SQLチューニング・ガイド: 結合の因数分解](https://docs.oracle.com/cd/F19136_01/tgsql/query-transformations.html#GUID-0D5B9093-CDEA-45AC-A607-1F0D8F2615DD)

- 結合述語のプッシュダウン(Join predicate pushdown)
[GAGA LIFE. : Oracle 問合せ変換③ 述語のプッシュ(Predicate Push Down)](https://www.undercoverlog.com/entry/2018/06/29/Oracle_%E5%95%8F%E5%90%88%E3%81%9B%E5%A4%89%E6%8F%9B%E2%91%A2_%E8%BF%B0%E8%AA%9E%E3%81%AE%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%28Predicate_Push_Down%29)

- 副問合せのネスト解除(Subquery Unnesting)
11.2.0.1から有効
副問合せの本体がネスト解除され、その副問合せを含む文の本体に結合する。
[Oracle Help Center SQL言語リファレンス](https://docs.oracle.com/cd/E82638_01/sqlrf/Unnesting-of-Nested-Subqueries.html#GUID-DA7A69AA-156D-4F1B-9E29-DAE9D230D9B5)

- 複数表の左側外部結合(Multi-table left outer joins)
12.1.0.1から有効
[津島博士のパフォーマンス講座　第42回　SQL自動変換について（３）](https://blogs.oracle.com/otnjp/post/tsushima-hakushi-42)

- スカラー副問合せのネスト解除(Scalar Subquery Unnesting)
12.1.0.1から有効
[津島博士のパフォーマンス講座　第42回　SQL自動変換について（３）](https://blogs.oracle.com/otnjp/post/tsushima-hakushi-42)
- 表拡張(Table Expansion)
11.2.0.1から有効
パーティション表のIndexに対して検索を行う際、Indexが無効になる検索方法でもIndex検索できるようにSQLを分割して実行してくれる機能
[Oracle® Database SQLチューニング・ガイド
12c リリース1 (12.1):問い合わせ変換](https://docs.oracle.com/cd/E57425_01/121/TGSQL/tgsql_transform.htm#GUID-6A6748FA-5803-4837-9A9F-525ED09BE481)

- フィルタ結合削除(Filtering Join Elimination)
不要な自己結合を防ぐための機能
同じテーブル(ex.T1)のキー同士で結合するSQLを書いた際、その結合に意味は無いためT1とT1はJoinされない。
[Striving for Optimal Performance](https://antognini.ch/2010/01/join-elimination/)

## バージョンアップで実行計画が変わってしまったら
状況に応じていずれかの対策を取る。

##### 1. Oracleのオプティマイザの一部機能だけOFF/ONにする。
オプティマイザのどの機能で実行計画への影響が出ているか分かれば、その機能を前の状態に戻す。
ただし、ON/OFF設定が可能な機能に限る。

##### 2. 特定SQLのオプティマイザの一部機能だけOFF/ONにする
#1でシステム全体の機能設定を変更できない場合は、SQLのヒント句で機能をON/OFFすることで、対象を特定SQLだけに絞る。

##### 3. システムのオプティマイザのバージョン設定を戻す
どのオプティマイズの機能が影響しているかわからないが、バージョン戻せば直る場合

 ```SQL:オプティマイザのバージョンの確認
 SHOW PARAMETER optimizer_features_enable
 ```
 ```SQL:オプティマイザのバージョンの変更
 ALTER SYSTEM SET OPTIMIZER_FEATURES_ENABLE='12.1.0.2';
 ```

##### 4. 特定SQLのオプティマイザのバージョン設定だけを戻す
#3でシステム全体の機能設定を変更できない場合は、SQLのヒント句で機能をON/OFFすることで、対象を特定SQLだけに絞る。
```SQL:オプティマイザのバージョンの変更
/*+ OPTIMIZER_FEATURES_ENABLE('12.1.0.2') */
```
##### 5. 実行計画の固定化
Sqlplus等の外部コンソールから元の実行計画で動くSQLを発行する。そこで出来た、実行計画をSPMで固定化する。
共有プールから実行計画がキャッシュアウトしたり、キャッシュクリアされた場合は再度同じことを実施する必要があるため、運用には向かない。暫定対策レベル。

[Qiita:Oracle12.2でSPMを使用して実行計画を固定化する](https://qiita.com/mkyz08/items/c220b4e3bd620ae8a905)

:::note warn
ハードパースが発生しなくても動作する機能「カーディナリティ・フィードバック」などが原因だと、この対策は無効です。
:::

##### 6. SQLの修正、ヒント句で元の実行計画になるように頑張る。
工数大。。

## バージョンアップ前に
Oracleは主に従来の統計情報運用では性能が発揮できないケースに対する機能を拡充しているため、統計情報の取得がうまく出来ていないテーブルでSQLが安定しないものがあれば、そのSQLだけ機能をONにした方が良さそう。
バージョンアップ前でOracleのオプティマイザの新機能に必要性を感じないのであれば、オプティマイザのバージョンはバージョンアップ前の設定にするのが安全。