ODP.NETによるデータ・アクセス・パフォーマンスの最適化

このチュートリアルでは、ODP.NETを使用して.NETアプリケーションのパフォーマンスを最適化する方法について説明します。

約20分

このチュートリアルでは、以下のトピックについて説明します。

このアイコンの上にカーソルを置くと、このチュートリアルのすべてのスクリーンショットがロードされて、表示されます。 (警告: すべてのスクリーンショットが同時にロードされるため、ご使用のインターネット接続によってはレスポンス・タイムが遅くなる場合があります。)

注: 各手順に関連したスクリーンショットのみをロードして表示する場合は、それぞれの手順にあるアイコンの上にカーソルを置いてください。 個々のスクリーンショットはクリックすると、非表示になります。

Oracleデータベースには、問合せの実行やクライアントからのデータの取得を最適化するためのメソッドが多数用意されています。 ODP.NETを使用すると、このようなパフォーマンスの最適化が可能なため、.NET開発者は、より効率的なデータベース・プログラムを作成できます。 このチュートリアルでは、ODP.NET開発者向けのもっとも一般的に使用されているパフォーマンス・チューニング・メソッドをいくつか紹介します。

最初のセクションでは、ステートメント・キャッシュについて説明します。 ステートメント・キャッシュは、initial文の実行中に作成されるサーバー・カーソルをキャッシングすることで、実行済みの各SQL文またはPL/SQL文の再解析を不要にします。 その後、同じ文を実行する際には、そのカーソルから解析された情報を再使用し、再解析せずに文を実行できるので、パフォーマンスが向上します。 ステートメント・キャッシュによるパフォーマンスの改善を確認するためには、繰り返し実行される文だけをキャッシングすることを推奨します。 さらに、SOL文またはPL/SQL文では、リテラル値ではなくパラメータを使用することで、ステートメント・キャッシュを最大限に活用できます。これは、パラメータ化された文から解析された情報は、その後の実行でパラメータの値が変わっている場合でも再使用できるためです。

2番目のセクションでは、ODP.NETでFetchSizeプロパティおよびRowSizeプロパティを使用して、データベース・ラウンドトリップごとに取得されるデータ量を制御する方法を示します。複数のラウンドトリップで1つのデータ・セットを取得すると効率が良くない場合があります。 この2つのプロパティを使用すると、開発者はデータベース・サーバーから取得するデータの量を微調整することができ、使用する必要があるデータベース・ラウンドトリップの回数を減らすことができます。

3番目のセクションでは、ODP.NETとOracleデータベース間でPL/SQL連想配列を受け渡しする方法を示します。 PL/SQL連想配列を使用すると、大量の同じデータ型のデータを.NET配列に渡すことができます。 このメソッドは、必要に応じてデータを1つのパラメータにまとめて、この2層間で受け渡しする柔軟で簡単な方法を提供します。

4番目のセクションでは、SQLをバッチ化する方法、Oracle REF Cursorの使用方法を示し、Multiple Active Result Sets(MARS)を実際に使用します。 SQLをバッチ化すると、開発者は複数のSQL文を1回のデータベース・ラウンドトリップで実行できます。 Oracle REF Cursorは、Oracleデータベースに固有のデータ型です。 固有の結果セット処理機能を提供しており、柔軟なデータ処理とチューニングが可能になります。 MARSはOracleで常にサポートされており、使用すると、単一の接続で一度に複数の結果セットをアクティブにできます。

注:このデモで示されるパフォーマンスの向上は、必ずしも本番環境で示される結果を反映するものではありません。 時には、設定、ハードウェア、データベース構成などの違いにより、実際の結果がデモの結果より良かったり、悪かったりします。たとえば、このデモを同じマシン上にあるクライアントとサーバーを使用して実行する場合、データベース・ラウンドトリップの回数を減らすことで得られるパフォーマンスの向上は、本番環境よりも大きい場合があります。これは、本番環境では通常、2つの異なるマシンにクライアントとサーバーが配置されているためです。

このチュートリアルを始める前に以下を確認してください。

Oracle Database 11gまたはOracle Database 10gデータベース・サーバーをインストールして作成してあること。

Visual Studio .NET 2003以上をインストールしてあること。

Oracle Database ClientまたはOTNから、ODP.NETをインストールしてあること。

ODP.NET Best Practices .zipダウンロードして、作業ディレクトリに解凍してあること。

ステートメント・キャッシュは、同じSQL文またはPL/SQL文を繰り返し実行する必要がある場合に有効です。 ODP.NETは、直前に使用された文をキャッシュします。 開発者は、直前に使用された文のうちどれをいくつキャッシュするかを決めます。 文がキャッシュされると、その文の解析ツリーが保持され、迅速に検索することができるので、パフォーマンスが向上します。 ODP.NETステートメント・キャッシュを使用するには、以下の手順を実行します。

1.

Visual Studioを起動します。 「File」→「Open」→「Project/Solution」を選択します。 「ODP.NET Performance Best Practices」ソリューションを選択して、「Open」をクリックします。

 

2.

View」→「Solution Explorer」を選択します。

 

3.

ODP.NET Best Practices.cs」をダブルクリックして、このチュートリアルのコードをオープンします。

 

4.

接続文字列を確認して、ODP.NETアプリケーションがOracleのサンプルHRスキーマに接続できるようにします。 conString文字列変数のPasswordおよびData Source値を変更する必要がある場合があります。

注: リモートのOracleデータベース・インスタンスにアクセスしている場合は、Data Sourceを//<hostname>/<SID>形式で指定する必要があります。

下方向にスクロールして、コードのDemo 1セクションに移動します。 コード・ファイルには、以下の手順でコードを入力する必要がある領域をマークするためのコメントが組み入れられています。 そのため、該当するコメント・セクションに適切なコードを入力するようにしてください。 これは、コードを正しい順序で入力し、ファイル内でコードを混同しないようにするためにおこないます。

 

5.

ステートメント・キャッシュの動作を確認するには、まずStatement Cache Sizeを0に設定して、1つの文を10,000回実行します。

Statement Cache Sizeを0に設定します。下のスクリーンショットに表示されているように、次のコードをcon1.ConnectionString文に追加します。

"Statement Cache Size=0";

 

6.

同じファイル内で下に移動し、ステートメント・キャッシュをオンにした状態で同じコードを実行します。 Statement Cache Sizeを1に設定して、同じ文を再度10,000回実行します。 下方向にスクロールして、下のスクリーンショットに示されているように、次のコードをcon2.ConnectionString文に追加します。

"Statement Cache Size=1";

ステートメントのキャッシングを有効にした状態で向上したパフォーマンスの割合がアプリケーションによって計算されます。

 

 

7.

これでコードの確認を終えたので、アプリケーションを実行して結果を確認できます。 「Build」→「Build Solution」を選択します。

出力ウィンドウにエラーも警告も表示されていないことを確認します。 コードは正常にコンパイルされる必要があります。

 

8.

Debug」→「Start Debugging」を選択するか、[F5]を押します。

コマンド・ウィンドウが開きます。 出力が表示されるのを待ちます。 ウィンドウは閉じないでください。

 

9.

結果は、スクリーンショットに表示されている結果とは若干異なる場合があります。

 

ご覧のとおり、繰り返し実行されるSQL文またはPL/SQL文に対してステートメント・キャッシュを使用すると、パフォーマンスを大幅に向上させることができます。 すべてのODP.NETアプリケーションに対してステートメント・キャッシュを有効にするために、Windows Registryでもステートメント・キャッシュを有効にできます。

[Enter]は押さないでください。 プログラムの実行を続行するために、後続のトピックでプログラムにコードを追加します。 コマンド・ウィンドウを閉じます。

 

データベース・ラウンドトリップごとにフェッチされるデータの量を制御すると、アプリケーションのパフォーマンスを最適化できます。 たとえば、エンド・ユーザーが行全体を使用する必要がある場合に、1回のラウンドトリップで行データの半分だけを取得するのは非効率的です。 ラウンドトリップを2回おこなう代わりに1回にすれば、パフォーマンスを最適化できるはずです。 ODP.NETを使用すると、開発者は問合せの行サイズを自動的に検出することができ、1回のラウンドトリップで取得する行数を指定できます。 この機能により、.NETプログラマはデータ取得の最適化をより簡単に実現できます。

このトピックでは、一度に1行をフェッチする問合せを実行し、次に、一度に100行をフェッチする同じ問合せを実行して、ラウンドトリップの回数を少なくすることによるパフォーマンスの向上を確認します。 以下の手順を実行します。

1.

最初に、OracleDataReaderでFetchSizeを1行に設定して問合せを実行します。

注:ODP.NETがOracleCommandを実行する場合、まず行の大きさを示すメタデータを取得して、RowSizeプロパティを移入します。 その後、開発者は、その行の数でRowSizeを乗算することで、1回のラウンドトリップでデータベースから取得する行数を設定できます。 この例では、1回のラウンドトリップで1行だけを取得します。

下方向にスクロールして、コードのDemo 2セクションに移動します。 forループを検索します。 次のコードを入力します。

reader.FetchSize = cmd.RowSize * 1;

 

 

2.

RowSizeは実行時に決定されるため、問合せや元となるスキーマを変更できますが、引き続きラウンドトリップごとに同じ数の行が取得されます。 この手順では、FetchSizeを100行に設定した場合と、FetchSizeを1行に設定した場合にかかる時間の差を比較できます。

下方向にスクロールして、次のforループに移動します。 次のコードを入力します。

reader.FetchSize = cmd.RowSize * 100;

 

 

3. 注:前のトピックで実行したDemo 1セクションのforループをコメント・アウトすれば、実行済みのデモに時間を費やさないようにできます。 ループだけをコメント・アウトすれば、チュートリアルの残りのトピックの結果には影響しません。 再度ループを繰り返し実行する時間を節約できます。

Demo 1セクションでforループをコメントにしていない場合は、Demo 1セクションの結果が出たら[Enter]を押して、このセクションでのプログラムの実行を続けます。

これでコードの確認を終えたので、アプリケーションを実行して結果を確認できます。 「Build」→「Build Solution」を選択します。

4.

Debug」→「Start Debugging」を選択するか、[F5]を押します。

 

5.

結果は、スクリーンショットに表示されている結果とは若干異なる場合があります。

 

[Enter]は押さないでください。 プログラムの実行を続行するために、後続のトピックでコードを追加します。 コマンド・ウィンドウを閉じます。

トピック・リストに戻る

このトピックでは、.NETとOracle間で配列パラメータを受け渡す方法について学習します。 配列パラメータを使用すると、1つのパラメータで大量のデータを渡すことができます。 この例では、入力配列パラメータを取得し、出力配列パラメータを返すストアド・プロシージャを使用します。

1.

Solution Explorerで、「array.sql」を右クリックして、「Open」を選択します。

 

 

2.

プロシージャを確認します。 PL/SQLストアド・プロシージャMYSPを使用して、PL/SQLパッケージMYPACKが作成されていることを確認します。 PL/SQL連想配列を使用するため、PL/SQLを使用する必要があります。 このストアド・プロシージャには、PL/SQL連想配列入力パラメータが設定されており、この値は出力用PL/SQL連想配列としてそのまま返されます。 ファイル内のすべてのコードを選択し、[Ctrl]+[C]を押して、クリップボードにコピーします。

 

 

3.

View」→「Server Explorer」を選択します。

 

 

4.

HRユーザー用のデータ接続を作成する必要があります。 データ接続の作成方法は、Oracle Developer Tools for Visual Studio .NETのチュートリアルで確認できます。 HR.ORCL接続を右クリックして、「Query Window」を選択します。 この例では、ORCLがデータベース・エイリアスですが、ご使用のシステムのデータベース・エイリアスとは異なる場合があります。

 

 

5.

[Ctrl]+[V]を押して、問合せウィンドウにコードを貼り付けます。 問合せウィンドウ内のすべてのテキストを選択して、「Execute」をクリックします。

 

 

6.

ストアド・プロシージャが正常に実行されたら、ODP.NET Best Practices.csコードに戻って、コードを確認します。

 

 

7.

下方向にスクロールして、Demo 3セクションに移動します。 次のコードを入力します。

cmd = new OracleCommand("MYPACK.MYSP", con);
cmd.CommandType = CommandType.StoredProcedure;

OracleParameter param1 = cmd.Parameters.Add("param1", OracleDbType.Varchar2);
OracleParameter param2 = cmd.Parameters.Add("param2", OracleDbType.Varchar2);
param1.CollectionType = OracleCollectionType.PLSQLAssociativeArray;
param2.CollectionType = OracleCollectionType.PLSQLAssociativeArray;

注: ODP.NETによって、MYPACK.MYSPストアド・プロシージャに対するコールが設定され、その配列パラメータがバインドされます。 配列の要素には、データ型Varchar2の値が含まれます。 配列がストアド・プロシージャで受け渡しされていることを示すために、ODP.NETは、Oracleデータベースのコレクション・データ型であるPL/SQL連想配列としてパラメータを設定します。

 

 

8.

ここで、パラメータの向き(入力または出力)を設定し、各パラメータの値を指定します。 param2をNULLに設定します。

次のコードを入力します。

param1.Direction = ParameterDirection.Input;
param2.Direction = ParameterDirection.Output;
param1.Value = new string[3]{"Oracle", "Database", "Rules"};
param2.Value = null;

 

 

9.

次に、配列内の要素の最大数(この例では3)、およびvarchar2の最大サイズ(この例では20文字)を指定します。

次のコードを入力します。

param1.Size = 3;
param2.Size = 3;
param1.ArrayBindSize = new int[3]{20,20,20};
param2.ArrayBindSize = new int[3]{20,20,20};

ストアド・プロシージャを実行して結果を出力するには、次のコードを入力します。

cmd.ExecuteNonQuery();

for(int i=0; i<3; i++)
{
Console.Write((param2.Value as OracleString[])[i]);
Console.WriteLine();
}

 

10. 注:前のトピックで実行したDemo 1セクションおよびDemo 2セクションのforループをコメント・アウトすれば、実行済みのデモに時間を費やさないようにできます。 ループだけをコメント・アウトすれば、チュートリアルの残りのトピックの結果には影響しません。 再度ループを繰り返し実行する時間を節約できます。

Demo 1セクションおよびDemo 2セクションのforループをコメントにしていない場合は、前の各デモの結果が表示されたあとで、[Enter]を計2回押して、このセクションでのプログラムの実行を続けます。

これでコードの確認を終えたので、アプリケーションを実行して結果を確認できます。 「Build」→「Build Solution」を選択します。

11.

Debug」→「Start Debugging」を選択するか、[F5]を押します。

 

12.

結果を検証します。

[Enter]は押さないでください。 プログラムの実行を続行するために、後続のトピックでプログラムにコードを追加します。 コマンド・ウィンドウを閉じます。

トピック・リストに戻る

複数のSQL文をバッチで実行して、データベース・ラウンドトリップの数を減らしたいと思うことはよくあるはずです。 ODP.NETでは、匿名のPL/SQLを介してこれを実現できます。 匿名のPL/SQLは、任意の数の問合せ、更新、挿入、および削除文をバッチ化するために使用できます。

このトピックでは、匿名のPL/SQLを使用して、1回のデータベース・ラウンドトリップで3つの問合せを実行します。 結果を取得するために、Oracle REF Cursorを使用して、いかに柔軟にデータベースからデータを取得できるかを確認できます。 REF Cursorでは、結果セットから必要なデータだけを取得できます。 最後に、ODP.NETが、同じ接続に対して複数のアクティブな結果セットをオープンしたままにできることを確認します。 以下の手順を実行します。

1.

ODP.NETにSQLをバッチ化させるために、匿名のPL/SQLを使用します。

注:匿名のPL/SQLは、"BEGIN"で始まり、"END;"で終わる文字列にすぎません。 これらの間に、1回のラウンドトリップで実行する必要があるSQL文を記載します。

この例では、3つの問合せをバッチ化して3つの結果セットを返します。 パラメータは、ODP.NETが問合せ結果をクライアントに返せるようにするためにバインドされます。 匿名のPL/SQLは、他のテキスト・コマンドと同様に実行されます。 そのため、コマンドはCommandType.Textとしてバインドされます。

下方向にスクロールして、コードのDemo 4セクションに移動します。 次のコードを入力します。

string cmdtxt = "BEGIN " +
"OPEN :1 for select FIRST_NAME, DEPARTMENT_ID from EMPLOYEES where DEPARTMENT_ID = 10; " +
"OPEN :2 for select FIRST_NAME, DEPARTMENT_ID from EMPLOYEES where DEPARTMENT_ID = 20; " +
"OPEN :3 for select FIRST_NAME, DEPARTMENT_ID from EMPLOYEES where DEPARTMENT_ID = 30; " +
"END;";
         

cmd = new OracleCommand(cmdtxt, con);
cmd.CommandType = CommandType.Text;

 

2.

次に、3つの出力REF Cursorをコマンドにバインドします。 この例では、それぞれのREF CursorでDEPARTMENT_ID 10、20、および30の従業員を選択します。

次のコードを入力します。

OracleParameter p1 = cmd.Parameters.Add("refcursor1", OracleDbType.RefCursor);
p1.Direction = ParameterDirection.Output;

OracleParameter p2 = cmd.Parameters.Add("refcursor2", OracleDbType.RefCursor);
p2.Direction = ParameterDirection.Output;

OracleParameter p3 = cmd.Parameters.Add("refcursor3", OracleDbType.RefCursor);
p3.Direction = ParameterDirection.Output;



3.

バッチ化した文を実行し、最初のパラメータから結果をフェッチせずに、2番目と3番目のパラメータからデータを取得します。 これが、REF Cursorを使用することの利点の1つであり、クライアントが要求する場合のみ、データを取得できます。 REF Cursorでは、フェッチする結果セット内のデータも個別に選択できます。 MARSを使用すると、両方のOracleDataReaderが同時にアクティブにデータを読み取っていることを確認できます。

関連するコメントの後に、以下のコードを入力します。

cmd.ExecuteNonQuery();

OracleDataReader dr1 = ((OracleRefCursor)cmd.Parameters[2].Value).GetDataReader();
OracleDataReader dr2 = ((OracleRefCursor)cmd.Parameters[1].Value).GetDataReader();

whileループを追加して、両方のDataReadersを同時に取得して、MARSが機能しているかどうかをテストします次のコードを入力して、2つのREF Cursorの結果を出力します。

while (dr1.Read() && dr2.Read())
{
Console.WriteLine("Employee Name: " + dr1.GetString(0) + ", " + 
           "Employee Dept:" + dr1.GetDecimal(1));
Console.WriteLine("Employee Name: " + dr2.GetString(0) + ", " + 
           "Employee Dept:" + dr2.GetDecimal(1)); 
Console.WriteLine();
}

 

4.

これでコードの確認を終えたので、アプリケーションを実行して結果を確認できます。 「Build」→「Build Solution」を選択します。

 

5.

Debug」→「Start Debugging」を選択するか、[F5]を押します。

 

6.

結果を検証します。

 

トピック・リストに戻る

このチュートリアルで学習した内容は、次のとおりです。

ステートメント・キャッシュの使用
FetchSizeおよびRowSizeの使用
配列パラメータの受け渡し
バッチSQL、REF Cursor、およびMultiple Active Result Sets(MARS)の使用

トピック・リストに戻る

このアイコンの上にカーソルを置くと、すべてのスクリーンショットが非表示になります。