ヘッダーをスキップ
Pro*C/C++プログラマーズ・ガイド
11g リリース1(11.1)
E05689-02
  目次
目次
索引
索引

戻る
戻る
 
次へ
次へ
 

C パフォーマンス・チューニング

この付録では、アプリケーションのパフォーマンスを改善するために簡単に適用できる方法をいくつか紹介します。これらの方法を使用すると、多くの場合、処理時間を25%以上削減できます。この付録の項目は、次のとおりです。

パフォーマンスを低下させる原因

パフォーマンスを低下させる原因の1つは通信オーバーヘッドが高いことです。サーバーでは、SQL文を一度に1つずつ処理する必要があります。つまり、文ごとに個別のコールが発生し、単一のオーバーヘッドが増加します。ネットワーク環境下では、ネットワークを介してSQL文を送信する必要があるため、ネットワークの通信量が増加することになります。ネットワークの通信量が多いと、アプリケーションの処理速度は著しく低下します。

パフォーマンスを低下させるもう1つの原因は非効率的なSQL文です。SQLはたいへん柔軟性に富むため、2つの異なる文から同一の結果を得ることもできますが、効率に差がある場合もあります。たとえば、次の2つのSELECT文は同じ行(従業員が最低1人いる部門ごとの名称および番号)を戻します。

EXEC SQL SELECT dname, deptno
    FROM dept
    WHERE deptno IN (SELECT deptno FROM emp);

EXEC SQL SELECT dname, deptno
    FROM dept
    WHERE EXISTS
    (SELECT deptno FROM emp WHERE dept.deptno = emp.deptno);

ただし、この最初の文はDEPT表内のすべての部門番号を探してEMP表全体をスキャンするため、処理に時間がかかります。EMP表内のDEPTNO列に索引を付けていても、この副問合せにはDEPTNOを指定するWHERE句がないため、索引は使用されません。

パフォーマンスを低下させる3番目の原因は、不要な解析とバインドです。SQL文を実行する前に、サーバーでこのSQL文を解析してバインドする必要があることに注意してください。解析とは、SQL文を調べて、これが構文規則に従って正しいデータベース・オブジェクトを参照していることを確認する作業です。バインドとは、SQL文内のホスト変数をそれぞれのアドレスに対応付け、サーバーがその値に対して読込みまたは書込みができるようにする処理です。

大部分のアプリケーションは、十分にカーソルを管理しているわけではありません。このため不要な解析またはバインドが発生し、結果的に処理のオーバーヘッドが著しく増加します。

パフォーマンスの改善方法

プリコンパイルしたプログラムのパフォーマンスがよくない場合でも、オーバーヘッドを減少させる方法はあります。

特にネットワーク化された環境下では、次の処理によって通信オーバーヘッドを大幅に削減できます。

処理のオーバーヘッドは、次の方法で大幅に削減できる場合があります。

以降の項では、オーバーヘッドを削減するための方法を検討します。

ホスト配列の使用

ホスト配列を使用すると、1つのSQL文でデータの集まり全体を操作できるため、パフォーマンスが向上します。たとえば、300人の従業員の給料をEMP表にINSERTする場合を考えてみます。配列がないと、プログラムは300の個々のINSERT(各従業員に1つ)を実行する必要があります。配列を使用すると、必要なINSERTは1回のみになります。次の文を考えてみます。

EXEC SQL INSERT INTO emp (sal) VALUES (:salary);

salaryが単純なホスト変数の場合は、サーバーでこのINSERT文を1回実行すると、EMP表には1行のみが挿入されます。この行のSAL列にはsalaryの値が格納されます。この方法で300行を挿入するには、このINSERT文を300回実行する必要があります。

しかし、salaryがサイズ300のホスト配列の場合は、一度に300行すべてがEMP表に挿入されます。各行のSAL列にはsalary配列の要素の値が格納されます。

詳細は、第8章「ホスト配列」を参照してください。

埋込みPL/SQLの使用

図C-1のように、アプリケーションがデータベース集約型であれば、制御構造体を使用してPL/SQLブロック内でSQL文をグループ化し、ブロック全体をデータベース・サーバーに送ることができます。これによってアプリケーションとデータベース・サーバーとの間の通信量は大幅に減少します。

また、PL/SQLサブプログラムを使用してアプリケーションからサーバーへのコールを少なくすることもできます。たとえば、個別のSQL文を実行するには10回のコールが必要ですが、10個のSQL文を含んでいるサブプログラムを実行するには、1回のコールで済みます。

図C-1 PL/SQLによるパフォーマンスの向上

PL/SQLによるパフォーマンスの向上
「図C-1 PL/SQLによるパフォーマンスの向上」の説明

PL/SQLは、Oracle FormsなどのOracleアプリケーション開発ツールでも使用できます。PL/SQLによってOracleのツール製品にプロシージャ型の処理能力が加えられるため、パフォーマンスが向上します。PL/SQLを使用すると、Oracleのツール製品ではデータベース・サーバーをコールせずに、すべての計算を迅速かつ効率的に処理できます。この結果、時間が節約され、ネットワークの通信量が減少します。

詳細は、第7章「埋込みPL/SQL」および『Oracle Database PL/SQL言語リファレンス』を参照してください。

SQL文の最適化

Oracleオプティマイザにより、すべてのSQL文について実行計画が生成されます。実行計画とは、サーバーでそのSQL文を実行するための一連の手順です。これらの手順は、『Oracle Databaseアプリケーション開発者ガイド - 基礎編』に記載されたルールによって決まります。これらのルールに従うと、最適なSQL文を作成できます。

オプティマイザ・ヒント

場合によっては、サーバーに対してSQL文を最適化する方法を示すことができます。このようにして示す内容はヒントと呼ばれ、これによりオプティマイザによる決定に運用側から影響を与えることができます。

ヒントはディレクティブではありません。オプティマイザによるジョブの実行を支援するのみです。ヒントの中には、SQL文の最適化に使用される情報のスコープを制限するものもあり、また総体的な方針を提示するものもあります。

ヒントを使用して、次の事項を指定できます。

  • SQL文のための最適化アプローチ

  • 参照されているそれぞれの表へのアクセス・パス

  • 結合のための結合順序

  • 表を結合するための方法

つまり、ヒントは次の4つのカテゴリに分けられます。

  • 最適化アプローチ

  • アクセス・パス

  • 結合順序

  • 結合操作

たとえば、2つの最適化アプローチ・ヒントであるCOSTとNOCOSTは、コストベースのオプティマイザとルールベースのオプティマイザをそれぞれ起動します。

SELECT、UPDATE、INSERTあるいはDELETE文の動詞の直後にC言語のスタイルのコメントを記述して、オプティマイザにヒントを与えます。たとえば、オプティマイザは次の文でコストベースのアプローチを使用します。

SELECT /*+ COST */ ename, sal INTO ...

C++コードでは、//+という形式のオプティマイザ・ヒントも認識されます。

オプティマイザ・ヒントの詳細は、『Oracle Databaseアドバンスト・アプリケーション開発者ガイド』を参照してください。

トレース機能

SQLトレース機能とEXPLAIN PLAN文を使用すると、アプリケーションの処理速度を低下させるおそれのあるSQL文を特定できます。

SQLトレース機能は、実行された各SQL文についての統計情報を生成します。これらの統計情報から、処理に最も時間のかかるSQL文を判断できます。このため、それらの文の処理効率のチューニングに専念できます。

EXPLAIN PLAN文はアプリケーション内の各SQL文に対する実行計画を示します。実行計画には、SQL文の実行に必要なデータベース処理が記述されています。実行計画を使用すると、非効率的なSQL文を特定できます。

これらのツールの使用方法および出力分析方法は、『Oracle Databaseアドバンスト・アプリケーション開発者ガイド』を参照してください。

文のキャッシュ

これは、動的SQL文に依存するすべてのプリコンパイラ・アプリケーションのパフォーマンス向上に役立つ機能です。この新機能の実装により、動的文を再利用する際の解析のオーバーヘッドが削減されます。プリコンパイラ・アプリケーションのユーザーは、新しいコマンドライン・オプション(文のキャッシュ・サイズ用)を使用することで、パフォーマンスの向上を実現できます。このオプションにより、動的文の文のキャッシュが有効になります。新しいオプションを有効にすることで、文のキャッシュがセッション作成時に作成されます。キャッシュは動的文に対してのみ適用され、静的文のカーソル・キャッシュとこの機能は共存します。

索引の使用

索引は、ROWIDを使用して、表の列のそれぞれの値をその値が入っている行に対応付けます。索引はCREATE INDEX文で作成します。詳細は、『Oracle Database SQL言語リファレンス』を参照してください。

表の15%未満の行しか戻さない問合せでは、索引を使用するとパフォーマンスが向上します。表の15%以上の行を戻す問合せは、全体スキャンによる方法、つまり、すべての行を順番に読み込む方法の方が速く処理されます。

WHERE句内で索引の付いた列を指定する問合せは、その索引を使用します。索引を付ける列を選択するためのガイドラインは、『Oracle Databaseアドバンスト・アプリケーション開発者ガイド』を参照してください。

行レベル・ロックの利用

デフォルトでは、データは表レベルではなく行レベルでロックされます。行レベルでロックすると、複数のユーザーが同一の表内の別の行に同時にアクセスできます。その結果、パフォーマンスが大幅に向上します。

表レベルでのロックも指定できますが、これはトランザクション処理オプションの効果を低下させます。表ロックの詳細は、「LOCK TABLEの使用」を参照してください。

オンラインのトランザクション処理を実行するアプリケーションには、行レベル・ロックが最も有効です。アプリケーションを表レベル・ロックで運用している場合は、行レベル・ロックを利用できるように変更してください。通常、明示的な表レベル・ロックは使用しないようにします。

不要な解析の排除

不要な解析をなくすには、カーソルを正しく操作することと、次に示すカーソル管理オプションを選択して使用する必要があります。

これらのオプションは、暗黙カーソルおよび明示カーソル、カーソル・キャッシュおよびプライベートSQL領域に影響します。

明示カーソルの操作

暗黙カーソルと明示カーソルの2種類のカーソルがあります。カーソルは、データ定義文およびDML文のすべてについて暗黙的に宣言されます。ただし、複数の行を戻す問合せについては、ユーザーが明示的にカーソルを宣言(つまりホスト配列を使用)する必要があります。DECLARE CURSOR文を使用すると、明示カーソルを宣言できます。明示カーソルのオープンおよびクローズを処理する方法はパフォーマンスに影響します。

アクティブ・セットを再評価する必要がある場合は、そのカーソルを再OPENするのみで済みます。OPENでは任意の新しいホスト変数値が使用されます。カーソルを再OPENする前にCLOSEしなければ、処理時間を節約できます。


注意:

すでにオープンしているカーソルを再OPENすると、パフォーマンスの最適化が簡単になります。ただし、これはANSI拡張機能です。したがって、MODE=ANSIを指定している場合には、カーソルを再OPENする前にCLOSEする必要があります。

カーソルのOPENによって取得したリソース(メモリーおよびロック)を解放するときにのみ、そのカーソルをCLOSEします。たとえば、プログラムでは終了前にすべてのカーソルをCLOSEする必要があります。

カーソルの制御

通常、明示的に宣言したカーソルを制御する方法は次の3つです。

  • DECLARE、OPENおよびCLOSEを使用します。

  • PREPARE、DECLARE、OPENおよびCLOSEを使用します。

  • MODE=ANSIの場合は、COMMITによってカーソルをクローズします。

最初の方法を使用する場合は、不要な解析に注意する必要があります。カーソルをCLOSEしたか、まだOPENしていないために、解析された文を使用できないときにかぎり、OPENで解析を実行します。プログラムはカーソルをDECLAREし、ホスト変数の値が変わるたびにこれを再OPENし、このSQL文が必要なくなったときにのみこれをCLOSEする必要があります。

2番目の方法(動的SQL方法3および方法4用の方法)を使用する場合は、PREPAREで解析が実行され、解析された文はCLOSEを実行するまで使用できます。プログラムで次のようにする必要があります。

  • SQL文をPREPAREします。

  • カーソルをDECLAREします。

  • ホスト変数の値が変更されるたびに、カーソルを再OPENします。

  • SQL文を再びPREPAREします。

  • SQL文が変更された場合は、カーソルを再OPENします。

  • SQL文が不要になった場合にのみカーソルをCLOSEします。

OPEN文およびCLOSE文をループの中に配置するのはできるだけ避けてください。SQL文の不要な再解析の原因になります。次の例では、OPEN文とCLOSE文がどちらも外側のwhileループの中にあります。MODE=ANSIの場合は、CLOSE文は例に示す位置に配置する必要があります。ANSIでは、カーソルを再OPENする前にCLOSEする必要があります。

EXEC SQL DECLARE emp_cursor CURSOR FOR
     SELECT ename, sal from emp where sal >  :salary and
                                      sal <= :salary + 1000;
salary = 0;
while (salary < 5000)
{
     EXEC SQL OPEN emp_cursor;
     while (SQLCODE==0)
     {
         EXEC SQL FETCH emp_cursor INTO ....
         ...
     }
     salary += 1000;
     EXEC SQL CLOSE emp_cursor;
}

一方、MODE=ORACLEのときは、カーソルを再OPENせずにCLOSE文を実行できます。CLOSE文を外側のwhileループの外に配置すると、OPEN文が繰り返されるたびに再解析されるのを回避できます。

...
while (salary < 5000)
{
     EXEC SQL OPEN emp_cursor;
     while (sqlca.sqlcode==0)
     {
         EXEC SQL FETCH emp_cursor INTO ....
         ...
     }
     salary += 1000;
}
EXEC SQL CLOSE emp_cursor;

カーソル管理オプションの使用

SQL文は、その構成を変更しないかぎり、一度のみ解析すれば十分です。たとえば、その選択リストまたはWHERE句に1列追加して、問合せの構成を変更します。HOLD_CURSORおよびRELEASE_CURSOR、MAXOPENCURSORSオプションによって、サーバーにおけるSQL文の解析および再解析の管理方法を制御できます。明示カーソルを宣言すると、解析を最大限に制御できます。

SQL領域とカーソル・キャッシュ

DML文を実行すると、その文に対応しているカーソルがPro*C/C++カーソル・キャッシュ内のエントリにリンクされます。カーソル・キャッシュとは、カーソル管理のために使用されて連続的に更新されるメモリー領域です。カーソル・キャッシュ・エントリは、次々に1つのプライベートSQL領域にリンクされます。

プライベートSQL領域とは、実行時に動的に作成される作業領域で、ホスト変数のアドレスおよびその文の処理に必要なその他の情報が保存されます。明示カーソルを使用すると、SQL文に名前を付け、プライベートSQL領域に保存されている情報にアクセスし、この情報の処理をある程度制御できます。

図C-2は、プログラムでINSERTおよびDELETEを実行した後のカーソル・キャッシュを表しています。

図C-2 カーソル・キャッシュでリンクされたカーソル

カーソル・キャッシュでリンクされたカーソル
「図C-2 カーソル・キャッシュでリンクされたカーソル」の説明

リソースの使用

ユーザー・セッションごとのオープン・カーソルの最大数は、初期化パラメータOPEN_CURSORSによって設定します。

MAXOPENCURSORSは、カーソル・キャッシュの初期サイズを指定します。新しいカーソルが必要で、しかも空きのキャッシュ・エントリがない場合、サーバーではエントリの再利用が試行されます。再利用の可能性はHOLD_CURSORとRELEASE_CURSORの値によって決まり、また明示カーソルの場合には、カーソル自身の状態によって決まります。

MAXOPENCURSORSの値が実際に必要なキャッシュ・エントリの数より小さい場合、サーバーでは再利用可能とマークされている最初のキャッシュ・エントリが使用されます。たとえば、INSERT文のキャッシュ・エントリE(1)が再利用可能とマークされていると、キャッシュ・エントリの数はMAXOPENCURSORSと等しくなります。プログラムが新しい文を実行する場合、キャッシュ・エントリE(1)とそのプライベートSQL領域は新しい文に再度割り当てられることがあります。INSERT文を再実行するために、サーバーではそれを再度解析しなおして、別のキャッシュ・エントリを再度割り当てる必要があります。

再利用できるキャッシュ・エントリが見つからない場合、サーバーでは追加のキャッシュ・エントリが割り当てられます。たとえば、MAXOPENCURSORS=8で、8エントリすべてがアクティブな場合、9番目のエントリが作成されます。サーバーでは、空きメモリーがなくなるかOPEN_CURSORSで設定された上限に達するまで、必要に応じてキャッシュ・エントリの割当てが続行されます。この動的割当ては、処理オーバーヘッドを増大させます。

したがって、MAXOPENCURSORSの値を小さく設定すると、メモリーの節約にはなりますが、新しいキャッシュ・エントリの動的割当ておよび割当て解除に資源を消耗する場合があります。MAXOPENCURSORSの値を大きく設定すると、実行は確実に速くなりますが、より大きなメモリーを使用することになります。

実行回数の少ない場合

実行回数の少ないSQL文とそのプライベートSQL領域間のリンクは、一時的なものにした方がよい場合もあります。

HOLD_CURSOR=NO(デフォルト値)と指定した場合は、サーバーによりそのSQL文が実行され、カーソルがクローズされた後に、プリコンパイラによりこのカーソルとカーソル・キャッシュ間のリンクが再利用可能としてマークされます。このリンクは、それが示すカーソル・キャッシュ・エントリが別のSQL文に必要になると、すぐに再使用されます。これにより、プライベートSQL領域に割り当てられたメモリーが解放され、解析ロックが解除されます。ただし、PREPAREしたカーソルは実行状態のままにする必要があるため、HOLD_CURSOR=NOと指定した場合でもそのリンクは維持されます。

RELEASE_CURSOR=YESと指定した場合は、サーバーによりそのSQL文が実行され、カーソルがクローズされた後に、プライベートSQL領域が自動的に解放され、解析した文は失われます。メモリーの節約のためにMAXOPENCURSORSを低い値に設定しているような場合には、この指定が必要です。

DML文がデータ定義文より前にあり、どちらの文も同じ表を参照する場合には、DML文にRELEASE_CURSOR=YESを指定してください。これにより、DML文が得る解析ロックと、データ定義文が得る排他ロックとの間の競合が回避されます。

RELEASE_CURSOR=YESを指定した場合は、プライベートSQL領域とキャッシュ・エントリ間のリンクはただちに削除され、このプライベートSQL領域は解放されます。HOLD_CURSOR=YESを指定している場合でも、RELEASE_CURSOR=YESによりHOLD_CURSOR=YESが上書きされるため、サーバーではSQL文を実行する前に、プライベートSQL領域にメモリーを再度割り当てて、このSQL文を再解析する必要があります。

ただし、RELEASE_CURSOR=YESを指定した場合、サーバーではSQL文とPL/SQLブロックの解析された表現が共有SQLキャッシュに保持されるため、それ以上再解析を処理する必要がないこともあります。カーソルをクローズしても、解析された表現はキャッシュの期限切れまで使用できます。

実行回数の多い場合

プライベートSQL領域にはSQL文の実行に必要なすべての情報が含まれるため、頻繁に実行されるSQL文とそのプライベートSQL領域のリンクを維持する必要があります。この情報へのアクセスを上手に管理すれば、後続の文の実行速度をさらに向上させることができます。

HOLD_CURSOR=YESを指定した場合は、サーバーによりSQL文が実行された後に、カーソルとカーソル・キャッシュのリンクが維持されます。したがって、解析された文と割り当てられたメモリーが、利用可能なまま維持されます。これは、不必要な再解析を避けるためにアクティブにしておくSQL文で役に立ちます。

RELEASE_CURSOR=NO(デフォルト値)の場合、サーバーでSQL文を実行した後にキャッシュ・エントリとプライベートSQL領域間のリンクが維持され、オープンしたカーソルの数がMAXOPENCURSORSの値を超えないかぎり、そのリンクは再利用されません。これは、解析した文および割り当てたメモリーが使用可能な状態のままのため、頻繁に実行するSQL文の場合に有効です。


注意:

Oracleの以前のバージョンでは、SQL文の実行後にRELEASE_CURSOR=NOおよびHOLD_CURSOR=YESとなっていると、解析後の表現を継続して使用できました。ただし、Oracle8iでは、RELEASE_CURSOR=NOおよびHOLD_CURSOR=YESが指定されている場合、解析後の表現を使用できるのは共有SQLキャッシュの内容が期限切れになるまでの間です。通常、このことは問題にはなりませんが、SQL文が再解析される前に参照されたオブジェクトの定義が変更されると、予期しない結果をもたらすことがあります。

埋込みPL/SQLの考慮事項

カーソルを管理する目的で、埋込みPL/SQLブロックはSQL文と同様に扱われます。埋込みPL/SQLブロックが実行されると、親カーソルはブロック全体に対応付けられ、キャッシュ・エントリと埋込みPL/SQLブロックに対するPGAのプライベートSQL領域との間にリングが作成されます。埋込みブロック内の各SQL文にも、PGAのプライベートSQL領域が必要であることに注意してください。それらのSQL文はPL/SQL自体で管理される子カーソルを使用します。子カーソルの性質は関連する親カーソルによって決定されます。つまり、子カーソルによって使用されるプライベートSQL領域は、親カーソルのプライベートSQL領域が解放されたときに解放されます。

パラメータの相互作用

表C-1に、HOLD_CURSORとRELEASE_CURSORの相互関係を示します。HOLD_CURSOR=NOを指定すると、RELEASE_CURSOR=NOは変更され、RELEASE_CURSOR=YESを指定すると、HOLD_CURSOR=YESが変更されることに注意してください。

表C-1 HOLD_CURSORとRELEASE_CURSORの相互関係

HOLD_CURSOR RELEASE_CURSOR リンク

NO

NO

再利用可能とマークされます。

YES

NO

維持されます。

NO

YES

ただちに削除されます。

YES

YES

ただちに削除されます。


不要な再解析の回避

不要な再解析を回避するには、ループ内のSQL文の実行フェーズ中に発生するエラーを排除する必要があります。ループ内の埋込みSQL文が実行されるときには、そのSQL文が一度のみ解析されます。ただし、実行結果がエラーになったSQL文は、通常は再解析されます。この場合、次のエラーを除き、発生したエラーすべてについて再解析が発生します。

他のすべてのエラーを排除すれば、不要な再解析を回避できます。

接続プーリングの使用方法

この項では、接続プーリングを使用したパフォーマンス・チューニングを説明します。アプリケーションがマルチスレッドで、同一データベースに対して同時操作を実行している場合、接続プーリング機能を使用するとパフォーマンスを上げられます。接続プーリングに使用されるパラメータに適切な値を選択してアプリケーションのパフォーマンスをチューニングすると、既存のアプリケーション・パフォーマンスに比べて、パフォーマンスを最高3倍まで上げられます。


関連項目