📖

Oracle SQLパフォーマンスチューニングに必要な実行計画とヒント句の活用

に公開

1. はじめに:なぜパフォーマンスチューニングが必要なのか

データベースアプリケーションの速度は、SQLのパフォーマンスに大きく左右されます。遅いSQLは、ユーザー体験の低下、バッチ処理時間の延長、ひいてはビジネス機会の損失にも繋がりかねません。

本記事では、OracleデータベースにおけるSQLのパフォーマンス問題を 「見える化」 し、「具体的な手を打つ」 ための手順を徹底解説します。特に、オプティマイザの「推定」とSQLの「実際の振る舞い」の違いに着目することで、より効果的なチューニングを実現します。


2. 必須ヒント句の活用:SQLの「真の姿」を掴む

パフォーマンスチューニングの第一歩は、SQLが内部でどのように実行されているかを正確に知ることです。以下のヒント句は、オプティマイザの推定値だけでなく、SQLの実際の実行時の振る舞いを正確に把握するために不可欠です。

2.1. GATHER_PLAN_STATISTICS:SQLの「実績値」を収集する

このヒント句は、SQLの 実際の実行時統計 を収集します。これにより、オプティマイザーの 推定値(E-Rows)実際の値(A-Rows) を比較し、その乖離からボトルネックを特定できます。

SELECT /*+ GATHER_PLAN_STATISTICS */
        column1, column2
FROM    table_name
WHERE   condition;
  • 役割と効果:

    • 実行時統計の収集: 実際の実行時間(A-Time)、処理行数(A-Rows)、バッファ使用量(Buffers)などの詳細な統計情報を取得します。

    • 正確な性能測定: オプティマイザーの「推定」ではなく、SQLが実際にどれだけのデータを処理し、どれだけの時間を要したかを把握できます。

    • ボトルネックの特定: 各処理ステップの「実績値」を見ることで、パフォーマンス上の真のボトルネックを特定しやすくなります。

2.2. NO_CURSOR_SHARING:特定のパラメータ値での「問題再現」を確実にする

Oracleは通常、SQLの再利用のためにカーソル共有を行います。しかし、異なるリテラル値でも同じカーソルを共有してしまうと、特定のパラメータ値でのみ発生するパフォーマンス問題を再現・分析するのが難しくなります。このヒント句は、そのカーソル共有を無効化し、リテラルSQLの調査時に特に有効です。

SELECT /*+ NO_CURSOR_SHARING */
        column1, column2
FROM    table_name
WHERE   id = 12345;
  • 役割と効果:

    • カーソル共有の無効化: 異なるリテラル値を持つSQLでもカーソルを共有しないようにします。これにより、特定のパラメータ値が原因で発生する性能問題を高い確度で再現しやすくなります。

    • 統計情報の精度向上: 各実行で独立した統計情報を収集するため、特定の条件での性能をより正確に測定できます。

    • テスト環境での正確な測定: 本番環境でバインド変数を使用していないリテラルSQLが存在する場合、そのリテラル値固有のパフォーマンス問題をテスト環境で再現・分析するのに役立ちます。

2.3. 両方のヒントを組み合わせた使用例

両方のヒントを組み合わせることで、特定のパラメータ値でのSQLの実際の挙動と、それに対するオプティマイザの判断を詳細に分析することが可能になります。

SELECT /*+ GATHER_PLAN_STATISTICS NO_CURSOR_SHARING */
        o.order_id,
        c.customer_name,
        p.product_name,
        o.order_date
FROM    orders o
        JOIN customers c ON o.customer_id = c.customer_id
        JOIN products p ON o.product_id = p.product_id
WHERE   o.order_date >= TO_DATE('2024-01-01', 'YYYY-MM-DD')
        AND c.region = 'Tokyo';

3. 実行計画の取得と分析:「見えない」処理を「見える化」する

SQLの実行パスを理解するためには、実行計画を確認します。Oracleにおける実行計画の取得にはDBMS_XPLAN.DISPLAY_CURSOREXPLAIN PLANがありますが、パフォーマンスチューニングにおいてはDBMS_XPLAN.DISPLAY_CURSORの使用が強く推奨されます。

3.1. DBMS_XPLAN.DISPLAY_CURSORV$SQLテーブルの連携:過去のSQLも深掘り

DBMS_XPLAN.DISPLAY_CURSORは、セッション内で最後に実行されたSQLだけでなく、特定のSQL_IDを持つSQLの実行計画と統計情報も表示できます。これは、他のセッションで実行されたSQLや、過去に実行されたSQLのパフォーマンスを分析する際に非常に役立ちます。

特定のSQLの情報を取得するには、まずV$SQLビューからそのSQLのSQL_IDCHILD_NUMBERを特定します。

-- 1. V$SQLビューから対象SQLのSQL_IDとCHILD_NUMBERを検索
-- SQL_TEXTの一部や実行ユーザーなどで絞り込むと便利です
SELECT sql_id, child_number, plan_hash_value, executions, elapsed_time, sql_fulltext
FROM    v$sql
WHERE   sql_fulltext LIKE '%SELECT /*+ GATHER_PLAN_STATISTICS NO_CURSOR_SHARING */%';
-- 例: 上記のSQLを実行した場合、そのSQL_IDとCHILD_NUMBERを確認できます。
-- 実行されたSQLの全文を指定して検索すると確実です。

目的のSQLのSQL_IDCHILD_NUMBERを特定したら、それらをDBMS_XPLAN.DISPLAY_CURSOR関数に渡して実行計画と統計情報を表示します。

-- 2. 特定のSQL_IDとCHILD_NUMBERを指定して実行計画と統計情報を表示
-- 取得したSQL_IDとCHILD_NUMBERをここに指定します
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('&sql_id', &child_number, 'ALLSTATS LAST'));
-- 例: SQL_IDが 'ayz123def456g'、CHILD_NUMBERが 0 の場合
-- SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('ayz123def456g', 0, 'ALLSTATS LAST'));

補足:

  • &sql_id&child_numberは、SQL Developerなどのツールで実行すると、値を入力するプロンプトが表示されます。SQL*Plusなどのコマンドラインツールでは、DEFINEコマンドで事前に変数を定義するか、直接値を記述してください。

  • ALLSTATS LASTオプションは、そのSQLが最後に実行された際のActual Statistics(実際の統計)を表示します。もし過去のすべての実行統計を含めたい場合は、ALLSTATSを使用します。

  • V$SQLビューは、共有プールに存在するSQLの情報を提供します。SQLが共有プールからパージされている場合は、このビューには表示されません。


4. EXPLAIN PLAN vs DBMS_XPLAN.DISPLAY_CURSOR:使い分けの極意

パフォーマンスチューニングにおいては、DBMS_XPLAN.DISPLAY_CURSOR(特にALLSTATS LASTオプション付き)が主に使用されます。これは、SQLの実際の実行時統計に基づいて分析できるためです。

DBMS_XPLAN.DISPLAY_CURSORの利点

  1. 実際の統計情報: A-Rows (実際の行数)、A-Time (実際の実行時間)、Buffers (論理読み取り数) など、SQLの実際の動作に基づいた詳細な統計が得られます。これにより、オプティマイザーの推定が正しかったか、どこでボトルネックが発生したかを正確に特定できます。

  2. バインド変数の考慮: バインド変数を使用するSQLの場合、DBMS_XPLAN.DISPLAY_CURSORは実際の実行を反映するため、より正確な計画を確認できます。これは、バインドピークスニッフィングなど、バインド値によって実行計画が変化するケースを分析する上で特に重要です。

  3. 動的サンプリングやアダプティブ実行計画の反映: 実行時にオプティマイザーの振る舞いが変わる場合も、DBMS_XPLAN.DISPLAY_CURSORはこれらの動的な変更を反映します。

EXPLAIN PLANの利点

  1. 非侵襲性: SQLを実際に実行しないため、システムに負荷をかけずに計画を事前に確認できます。特にデータ更新系のSQLや、実行に時間がかかるSQLの場合に有用です。

  2. 計画の比較: 複数のSQL文やヒント句の組み合わせなど、異なるシナリオでの実行計画を迅速に比較したい場合に便利です。例えば、インデックス追加前後の計画を比較する際などに有効です。

結論: パフォーマンス問題の根本原因を究明し、具体的なチューニング効果を測定するためには、DBMS_XPLAN.DISPLAY_CURSORが不可欠です。EXPLAIN PLANは、SQLの変更や新規開発時に計画を概ね把握するための補助ツールとして活用しましょう。


5. 実行計画の読み方:ボトルネックを見つける「目」を養う

実行計画を分析し、パフォーマンス問題の原因を特定します。

5.1. 実行計画の基本構造と「実績値」

Plan hash value: 1234567890

---------------------------------------------------------------------------
| Id | Operation           | Name    | Rows | Bytes | Cost (%CPU)| Time    |
---------------------------------------------------------------------------
|  0 | SELECT STATEMENT    |         |    1 |    20 |     3  (0) | 00:00:01 |
|  1 |  NESTED LOOPS       |         |    1 |    20 |     3  (0) | 00:00:01 |
|  2 |   TABLE ACCESS FULL | ORDERS  |    1 |    10 |     2  (0) | 00:00:01 |
|  3 |   INDEX UNIQUE SCAN | PK_CUST |    1 |    10 |     1  (0) | 00:00:01 |
---------------------------------------------------------------------------

この基本形に、DBMS_XPLAN.DISPLAY_CURSORALLSTATS LASTを指定した場合に表示される**「Actual Statistics(実績統計)」**が、チューニングにおいて最も重視すべき指標となります。

Id Operation Name Starts E-Rows A-Rows A-Time Buffers
0 SELECT STATEMENT 1 1 100 00:00:01 150
1 NESTED LOOPS 1 1 100 00:00:01 150
2 TABLE ACCESS ORDERS 1 1 1,000 00:00:01 100
3 INDEX SCAN PK_CUST 1,000 1 100 00:00:01 50
  • Id: 操作の順序を示す番号(インデントが深いものから実行)。

  • Operation: 実行される操作の種類。

  • Name: 対象となるテーブルやインデックスの名前。

  • Starts: その操作が開始された回数。例えば、ネステッドループ結合の内部ループが実行された回数などを示します。

  • E-Rows: 推定行数(Estimated Rows)。オプティマイザーが統計情報に基づいて算出した予測値です。

  • A-Rows: 実際の行数(Actual Rows)。その操作で実際に処理された行数を示し、最も重要な指標の一つです。

  • A-Time: 実際の実行時間(Actual Time)。その操作にかかった実際の時間で、ボトルネックを特定する上で重要です。

  • Buffers: 使用された論理読み取りのバッファブロック数。ディスクI/Oだけでなく、キャッシュからの読み取りも含まれます。I/O負荷の目安となります。

5.2. 実行計画の読み方の手順:データフローとボトルネックを追う

  1. インデントの深い順から実行される: 最も深いインデントから順番に実行されます。同じレベルの場合は上から下へ実行されます。これがデータがどのように処理されていくかのフローです。

  2. データフローを追跡する: Idとインデントを頼りに、データがどのように各操作を流れていくかを追跡し、SQL全体の処理の流れを把握します。

  3. A-RowsA-Time でボトルネックを特定する:

    • A-RowsE-Rowsより著しく大きい操作: オプティマイザーの推定と実際の乖離が大きく、問題の根源である可能性が高いです。オプティマイザが最適な計画を選択できなかった原因を探ります。

    • A-Timeが大きい操作: 特定の操作に実行時間が集中している場合、その操作がボトルネックです。この操作に要する時間を短縮することが最優先の課題となります。

5.3. 主要な操作タイプと最適化のポイント

Oracleの実行計画に頻繁に登場する主要な操作タイプと、それぞれの最適化のポイントを理解しておきましょう。

  • TABLE ACCESS FULL(フルテーブルスキャン)

    • 特徴: テーブル全体を順次読み取ります。大量のデータを読み込むため、大規模なテーブルでは性能劣化の要因になりやすいです。

    • 最適化: WHERE句の条件に対する適切なインデックスの作成を検討します。ただし、テーブルのほとんどの行にアクセスする場合はフルテーブルスキャンの方が効率的な場合もあります。

  • INDEX RANGE SCAN(インデックス範囲スキャン)

    • 特徴: インデックスを使用して、特定の範囲のデータを効率的に検索します。

    • 最適化: 検索条件に対するインデックスのカーディナリティセレクティビティを確認します。インデックスが適切に利用されるように、WHERE句の記述を見直すことも重要です(例: LIKE '%ABC'のような前方一致以外の条件はインデックスを使いにくい)。

  • NESTED LOOPS(ネステッドループ結合)

    • 特徴: 外部ループから読み込んだ行に対して、内部ループを繰り返し実行して結合します。外部ループの行数が少なく、内部ループ側にインデックスが有効な場合に効率的です。

    • 最適化: 結合順序と内部ループ側のテーブルに対するインデックスの有無と有効性が重要です。特に外部ループの行数が多くなると性能が劣化しやすいため注意が必要です。

  • HASH JOIN(ハッシュ結合)

    • 特徴: 2つのテーブルのいずれか(通常は小さい方)をハッシュ表に構築し、もう一方のテーブルをスキャンしながら結合します。結合するデータ量が比較的多い場合に効率的です。メモリを多く使用するため、PGA(Program Global Area)の設定も影響します。
  • MERGE JOIN(ソートマージ結合)

    • 特徴: 両方のテーブルを結合キーでソートし、ソートされた結果をマージしながら結合します。結合する両方のデータが既にソートされている場合や、非常に大規模な結合でソート処理のコストが相対的に低い場合に効率的です。

5.4. E-RowsA-Rows の差異が大きい場合の対処法

オプティマイザーの推定と実際の行数に大きな差異がある場合、オプティマイザーが不適切な実行計画を選択している可能性が高いです。以下の対処法を検討します。

  1. 統計情報の更新:

    • 古い、または不足している統計情報が原因で、オプティマイザーが誤った計画を立てている可能性があります。データが大幅に増減した場合や、DML(INSERT/UPDATE/DELETE)が頻繁に行われるテーブルでは、統計情報を最新に保つことが非常に重要です。

    • 特に一時テーブル(Global Temporary Tableなど)は、デフォルトでは統計情報が存在しない、または古い場合が多いため、これが原因でE-RowsA-Rowsの乖離が生じやすい傾向にあります。

  2. ヒストグラムの作成:

    • 列にデータ分布の偏りがある場合(例: ほとんどの行が特定の値を持つ「性別」列など)、通常の統計情報だけではオプティマイザーはその偏りを正確に把握できません。

    • ヒストグラムを作成することで、オプティマイザーがその偏りを考慮し、より適切な実行計画を選択できるようになります。

    EXEC DBMS_STATS.GATHER_TABLE_STATS('YOUR_SCHEMA_NAME', 'ORDERS',
              method_opt => 'FOR COLUMNS status SIZE AUTO');
    
  3. ヒント句による強制(最終手段):

    • 統計情報を更新しても計画が改善しない場合や、特定の状況下でのみ問題が発生する場合、ヒント句で明示的に結合方式やアクセスパスを強制することができます。

    • ただし、ヒント句の安易な利用は避けましょう。環境やデータ量の変化によって性能が劣化するリスクがあるため、一時的な対策として利用を検討します。永続的な解決策は、統計情報の最適化やSQL/スキーマの根本的な見直しです。基本的には非推奨です。

    SELECT /*+ GATHER_PLAN_STATISTICS NO_CURSOR_SHARING USE_HASH(o c) */
            o.order_id, c.customer_name
    FROM    orders o JOIN customers c ON o.customer_id = c.customer_id
    WHERE   o.status = 'SHIPPED';
    
  4. 動的統計の活用:

    • 複雑なSQLや、カーディナリティの推定が難しい述語(例: 算術式を含む条件)の場合に、動的サンプリングを利用して実行計画の精度を改善できます。

    • 動的サンプリングは、SQL実行時に一時的にテーブルの一部をサンプリングして統計情報を収集し、それに基づいて実行計画を生成する機能です。

    SELECT /*+ GATHER_PLAN_STATISTICS NO_CURSOR_SHARING DYNAMIC_SAMPLING(4) */
            column1, column2
    FROM    table_name
    WHERE   complex_condition;
    

5.5. 差異確認のチェックリスト:パフォーマンス問題の兆候を見抜く

実行計画の分析時には、以下の点に注目して問題の兆候を捉えます。

  • E-Rows vs A-Rows: 10倍以上の差異がある場合は要注意(最優先で確認すべき項目)。オプティマイザーがデータ量を過小または過大に評価している可能性が高いです。

  • 大量行処理: A-Rowsが想定以上に多い操作を特定します。特に、インデックスを利用できるはずの箇所で大量の行を処理している場合は、インデックスの問題や不適切なアクセスパスが考えられます。

  • Starts: 想定される実行回数と大きく異なる場合。特に、ネステッドループ結合で内部ループのStartsが予想以上に多い場合、外部ループから渡される行数が多すぎる可能性があります。

  • A-Time: 特定の操作に時間が集中している場合。その操作がボトルネックであり、その操作を改善することが全体のパフォーマンス向上に直結します。

  • Buffers: 想定以上のI/O(論理読み取り)が発生している場合。これは、不必要なデータ読み込みや、キャッシュ効率の悪化を示唆します。

  • 統計情報の更新日: DBA_TAB_STATISTICSDBA_PART_COL_STATISTICSビューなどで、対象テーブルやカラムの統計情報が最新のデータ状況を反映しているかを確認します。

重要: コスト値は統計情報の精度に依存するため、実際の処理行数(A-Rows)や実行時間(A-Time)との差異がある場合は参考程度に留めることが重要です。コスト値のみを見て判断すると、誤ったチューニングに繋がる可能性があります。


6. インデックスとカーディナリティ:データアクセスを最適化する

インデックスはデータ検索を高速化するための仕組みです。その有効性を判断する上で「カーディナリティ」が非常に重要になります。

6.1. カーディナリティとは

カーディナリティ(Cardinality)は、テーブルの列に含まれる一意な値の数を指します。カーディナリティが高いほど、その列の値はよりユニークであると言えます。

例:顧客テーブルの場合

CREATE TABLE customers (
    customer_id   NUMBER PRIMARY KEY,       -- カーディナリティ: 高(全て一意)
    customer_name VARCHAR2(100),            -- カーディナリティ: 高(ほぼ一意)
    gender        CHAR(1),                  -- カーディナリティ: 低(M/F の2値)
    age           NUMBER,                   -- カーディナリティ: 中(1-100程度)
    region        VARCHAR2(50)              -- カーディナリティ: 中(都道府県レベル)
);

6.2. カーディナリティの分類とインデックス効果

一般的に、カーディナリティは以下の3段階に分類されます。

  • 高カーディナリティ: 一意な値が多い(例: 主キー、メールアドレス)。特定の行を高速に検索する際に非常に効果的であり、インデックスの効果は非常に高いです。

  • 中カーディナリティ: 適度に分散した値(例: 日付、地域コード)。状況に応じてインデックス効果がありますが、検索条件によってはフルテーブルスキャンの方が効率的になる場合もあります。

  • 低カーディナリティ: 限られた値の種類(例: 性別、ステータス、フラグ)。単一のインデックスを作成しても、選択性が低すぎるため、インデックススキャンではなくフルテーブルスキャンが選択されることが多いです。

6.3. カーディナリティの確認方法:データの実態を知る

インデックス戦略を検討する上で、実際のデータにおけるカーディナリティとデータ分布を確認することが重要です。

  1. 基本的なカーディナリティ確認: 特定の列の一意な値の数、総行数、そして選択率(セレクティビティ)を計算します。

    SELECT COUNT(DISTINCT column_name) as cardinality,
           COUNT(*) as total_rows,
           ROUND(COUNT(DISTINCT column_name) / COUNT(*) * 100, 2) as selectivity_percent
    FROM table_name;
    
  2. データ分布の確認: 各値の出現頻度を確認し、データに偏りがないかを把握します。低カーディナリティの列では、特定の値が全体の大部分を占めることがあります。

    SELECT column_name, COUNT(*) as frequency
    FROM table_name
    GROUP BY column_name
    ORDER BY frequency DESC;
    
  3. 統計情報ビューからの確認: user_tab_col_statisticsビューは、オプティマイザーが使用する統計情報(カラムのカーディナリティなど)を直接参照できます。

    SELECT
        table_name,
        column_name,
        num_distinct as cardinality,
        num_rows as total_rows,
        ROUND(num_distinct / num_rows, 4) as selectivity
    FROM user_tab_col_statistics
    WHERE table_name = 'CUSTOMERS';
    

6.4. カーディナリティに基づくインデックス戦略

カーディナリティの特性を理解することで、より効果的なインデックス戦略を立てることができます。

  • 高カーディナリティ列へのインデックス: 特定の行をピンポイントで検索する際に非常に効果的です。WHERE email = '...' のような検索で威力を発揮します。

    CREATE INDEX idx_customer_email ON customers(email);
    
  • 低カーディナリティ列への対応: 単一の低カーディナリティ列にインデックスを作成しても効果が薄い場合が多いため、以下の方法を検討します。

    • 複合インデックス: 他の列と組み合わせて全体の選択性を高めます。複数の条件で絞り込むSQLの場合に有効です。例えば、status(低カーディナリティ)とorder_date(高カーディナリティ)を組み合わせることで、特定の期間における特定のステータスの注文を効率的に検索できます。
    CREATE INDEX idx_status_date ON orders(status, order_date);
    
    • 関数インデックス: WHERE句で関数(例: UPPER, TRUNC)を適用している場合、そのままでは通常のインデックスが利用されません。関数インデックスを作成することで、インデックスが利用可能になります。
    CREATE INDEX idx_upper_name ON customers(UPPER(customer_name));
    

6.5. セレクティビティ(選択性)の重要性

セレクティビティは、$ \text{セレクティビティ} = \frac{\text{カーディナリティ}}{\text{総行数}} $ で計算され、列の選択性(つまり、その列で絞り込んだ際にどれだけ行数が絞り込まれるか)を表します。値が小さいほどインデックスの効果が高まります。

  • セレクティビティが小さい(例: 0.01以下、つまり1%以下): 検索条件によってデータが大幅に絞り込まれるため、インデックスが非常に効果的です。

  • セレクティビティが大きい(例: 0.1以上、つまり10%以上): 検索条件で絞り込める行数が少ないため、インデックススキャンとテーブルアクセスを繰り返すよりも、フルテーブルスキャンの方が効率的と判断されることがあります。


7. 実践的なチューニング手順:サイクルを回して改善する

パフォーマンスチューニングは、以下のサイクルで実行します。

7.1. パフォーマンス問題の特定:「どこが遅い?」

  1. 問題のあるSQLの特定:

    AWSのPerformance Insights、V$SQLビューなどを活用し、CPU使用率、I/O待機時間、実行回数などから、システム全体のボトルネックとなっているSQLや、ユーザー体感として遅いSQLを特定します。

  2. 実行統計付きでSQLを実行:

    特定したSQLにGATHER_PLAN_STATISTICSとNO_CURSOR_SHARINGヒントを付与して実行し、実際の実行統計を収集します。これにより、オプティマイザの推定と実際の乖離を評価できます。

    SELECT /*+ GATHER_PLAN_STATISTICS NO_CURSOR_SHARING */
            o.order_id, c.customer_name
    FROM    orders o JOIN customers c ON o.customer_id = c.customer_id
    WHERE   o.order_date >= SYSDATE - 30;
    
  3. 実行計画と統計を確認:

    DBMS_XPLAN.DISPLAY_CURSORを使って、実行計画とActual Statistics(A-Rows、A-Time、Buffers)を確認します。

    SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(null, null, 'ALLSTATS LAST'));
    

    A-RowsE-Rowsの乖離、A-Timeの集中、Buffersの多さなどを確認し、どの操作が問題の原因となっているかを特定します。

7.2. 改善施策の実施:「どう改善する?」

問題の原因に応じた適切な改善策を実施します。

  • 問題: フルテーブルスキャンが発生し、A-Timeが長い

    • 解決: 適切なインデックスの作成

      WHERE句やJOIN句の条件に合致するカラムにインデックスがないか、既存のインデックスが利用されていない場合に検討します。

    CREATE INDEX idx_orders_date ON orders(order_date);
    
  • 問題: 結合順序が非効率で、NESTED LOOPSStartsが多い

    • 解決: ヒント句で結合順序を制御、または統計情報を更新

      LEADINGヒント句で結合の駆動表を指定したり、統計情報が古いためにオプティマイザが誤った結合順序を選択している場合は統計情報の更新を検討します。

    SELECT /*+ GATHER_PLAN_STATISTICS NO_CURSOR_SHARING LEADING(c o) */
            o.order_id, c.customer_name
    FROM    orders o JOIN customers c ON o.customer_id = c.customer_id
    WHERE   o.order_date >= SYSDATE - 30;
    
  • 問題: E-RowsA-Rowsの乖離が大きい

    • 解決: 統計情報の更新やヒストグラムの作成、動的統計の活用

      オプティマイザが正確な行数を予測できていないため、統計情報を最新にする、データ分布に偏りがあればヒストグラムを作成する、あるいは動的統計を利用するなどを検討します。

  • 問題: SQL_IDが変わってしまい、パフォーマンス問題が再現できない

    • 解決: バインド変数の導入

      リテラル値を使用しているSQLがある場合、バインド変数にすることでSQL_IDの安定化とカーソル共有の促進に繋がります。これにより、SQL実行計画のキャッシュヒット率が向上し、性能が安定しやすくなります。

7.3. 効果測定と検証:「本当に速くなった?」

改善策実施後、必ず効果を測定し検証します。改善前後の比較が非常に重要です。

  • 改善前後の比較:

    再度、対象SQLを実行統計付きで実行し、改善前と比較して実行時間、バッファ使用量、CPU使用率などがどのように変化したかを評価します。

    V$SQLビューから、特定のSQL_IDやSQL_TEXTでパフォーマンスメトリクスを比較できます。

    SELECT sql_text,
           executions,
           elapsed_time/executions as avg_elapsed_time,
           buffer_gets/executions as avg_buffer_gets
    FROM v$sql
    WHERE sql_text LIKE '%your_query%';
    

    可能であれば、システム全体のパフォーマンスモニタリングツール(AWR、Statspackなど)も参照し、チューニングがシステム全体に与える影響も確認します。

7.4. チューニング環境におけるデータ量の重要性:本番を「再現」する

チューニングを行うテスト環境では、本番環境と同等か、それ以上のデータ量を必ず確保してください。

SQLの実行計画はデータ量やデータ分布に大きく左右されるため、データ量が少ない環境でチューニングを行っても、本番環境で同じパフォーマンスが得られない可能性が非常に高いです。オプティマイザーの判断を正確に評価し、本番での問題を再現するためには、実際のデータボリュームとデータ分布を再現した環境が不可欠です。


8. まとめ:パフォーマンス改善は「継続的な探偵作業」

Oracleのパフォーマンスチューニングは、一度やれば終わりではありません。データ量の変化、アクセスパターンの変化、アプリケーションの改修など、様々な要因でパフォーマンスは変動します。まさに、継続的な分析と改善を要する「探偵作業」です。

以下のポイントを押さえることで、効果的にパフォーマンスを向上させ、安定稼働に貢献できます。

  • 正確な情報収集: GATHER_PLAN_STATISTICSDBMS_XPLAN.DISPLAY_CURSOR('ALLSTATS LAST')を積極的に活用し、SQLの実際の動きを正確に把握することが成功の鍵です。

  • 推定値と実際値の差異分析: E-RowsA-Rowsの乖離からボトルネックを特定し、オプティマイザの判断の誤りを見つけ出します。特に一時テーブルの統計情報には注意が必要です。

  • インデックス戦略: カーディナリティを理解し、クエリパターンに応じた最適なインデックスを設計します。低カーディナリティの列には複合インデックスや関数インデックスも視野に入れます。

  • テスト環境のデータ量: 本番同等以上のデータ量とデータ分布を再現した環境でチューニングを行うことで、本番環境での効果を確実にします。

  • 継続的な監視: 定期的な実行計画の確認と統計情報の更新を行い、パフォーマンスの維持に努めましょう。新たなパフォーマンス問題が発生した際には、このガイドの手順に沿って迅速に対応してください。

このガイドが、皆さんのOracleパフォーマンスチューニングの一助となれば幸いです。もし他に知りたいことや、具体的なケースがあれば、ぜひコメントで教えてください!

Discussion