|
ここでは、.NET アプリケーションを設計するときに、システム パフォーマンスを最適化する方法を説明します。
アプリケーションのパフォーマンスを向上させるには、接続を適切に管理することが重要です。接続を複数回行う代わりに、1 回の接続で複数のステートメント オブジェクトを使用することで、アプリケーションのパフォーマンスを最適化します。初期接続の確立後は、データソースへの接続は行わないようにします。
接続プールを使用すると、特に、ネットワークまたは World Wide Web を介して接続するアプリケーションのパフォーマンスを大幅に向上させることができます。また、接続プールによって接続の再利用が可能になります。接続を閉じても、データベースとの物理的接続を閉じるわけではありません。アプリケーションが接続を要求すると、アクティブな接続が再利用されるので、新しい接続の作成に必要なネットワークへの I/O は発生しません。
接続は、あらかじめ割り当てておきます。まず、どの接続文字列が必要かを確認します。1 つの固有な接続文字列で、新しい接続プールが 1 つ作成されます。
一度作成された接続プールは、アクティブなプロセスが終了するか、Connection Lifetime に指定された時間が過ぎるまで破棄されません。アクティブでないプールや空のプールを維持するのに必要なシステム オーバーヘッドは、ごくわずかです。
接続とステートメントの処理は、実装前に決めておく必要があります。接続方法を慎重に管理することで、アプリケーションのパフォーマンスが向上し、メンテナンスも簡単になります。
接続は、それが必要になる直前に開いてください。必要になるより早く接続を開くと、ほかのユーザーが使用できる接続の数が減り、リソースの需要が増える可能性があります。
使用可能なリソースを保持するには、接続が必要でなくなったらすぐに接続を明示的に閉じます。有効範囲外になった接続がガベージ コレクターによって暗黙的にクリーンアップされるのを待つ場合は、接続は直ちに接続プールに戻されず、実際には使用されていないリソースに関連付けられます。
finally ブロックの内側で接続を閉じます。finally ブロック内のコードは、例外が発生した場合でも必ず実行されます。これにより、接続が明示的に閉じられることが保証されます。たとえば、次のようにします。
try { DBConn.Open(); ... // 必要な作業を行います } catch (Exception ex) { // 例外を処理します } finally { // 接続を閉じます if (DBConn != null) DBConn.Close(); }
接続プールを使用している場合には、接続の開閉は不経済な操作ではありません。データ プロバイダーの Connection オブジェクトの Close() メソッドを使用すると、接続は接続プールに追加されるか戻されます。ただし、自動的に接続を閉じると、その接続に関連付けられているすべての DataReader オブジェクトが閉じられることを忘れないでください。
ステートメント キャッシュは、プリペアド ステートメントのグループまたは Command オブジェクトのインスタンスで、アプリケーションによって再使用が可能です。ステートメント キャッシュを使用するとアプリケーションのパフォーマンスを向上させることができます。これは、プリペアド ステートメントの動作が、そのステートメントがアプリケーションの存続期間中に何度再使用されたとしても、1 度だけ実行されるためです。
ステートメント キャッシュは物理接続に属します。実行された後、プリペアド ステートメントはステートメント キャッシュに置かれ、接続が閉じられるまで保持されます。
アプリケーションが使用する全プリペアド ステートメントをキャッシュすれば、パフォーマンスが向上するように思われます。しかし、この手法では、接続プールを使ってステートメント キャッシングを実装した場合、データベースのメモリに負担をかける結果になります。この場合、プールされた各接続がステートメント キャッシュを持ち、アプリケーションで使用される全プリペアド ステートメントを各自のキャッシュに含むことになります。これらのプールされたプリペアド ステートメントは、すべてデータベースのメモリにも保持されます。
Command.Prepare メソッドを使用するかどうかによって、クエリ実行のパフォーマンスは良くも悪くも大きな影響を受けます。Command.Prepare メソッドは、基となるデータ プロバイダーに対し、パラメーター マーカーを使用するステートメントの複数回実行を最適化するように指示します。実行メソッド(ExecuteReader、ExecuteNonQuery、または ExecuteScalar)が使用されているかどうかにかかわらず、あらゆるコマンドの準備が可能であることに留意してください。
ADO.NET データ プロバイダーが、プリペアド ステートメントを含んでいるストアド プロシージャをサーバー上で作成することにより、Command.Prepare を実装する場合を考えてみましょう。ストアド プロシージャの作成には多くのオーバーヘッドを要しますが、ステートメントは複数回実行することができます。ストアド プロシージャの作成はパフォーマンスに悪影響を与えますが、プロシージャの作成時にクエリが解析され、最適化パスが保管されるため、作成したステートメントの実行は最小化されます。同じステートメントを複数回実行するアプリケーションは、Command.Prepare を呼び出してからそのコマンドを複数回実行することで、大きなメリットを得ることができます。
しかし、1 回しか実行しないステートメントに対して Command.Prepare を使用すると、不要なオーバーヘッドが生じる結果になります。さらに、大きな単一の実行クエリ バッチに対して Command.Prepare を使用するアプリケーションではパフォーマンスが低下します。同様に、常に Command.Prepare を使用するか、まったく Command.Prepare を使用しないアプリケーションは、プリペアド ステートメントとアンプリペアド ステートメントを論理的に組み合わせて使用するアプリケーションとは同じように機能しません。
アンマネージ コード、つまり .NET 環境外のコードへのブリッジは、パフォーマンスを低下させます。マネージ コードからアンマネージ コードを呼び出すと、データ プロバイダーの実行速度はマネージ コードのみのデータ プロバイダーよりも著しく遅くなります。このようなパフォーマンスが大きく落ちる方法は、極力避けます。
ブリッジを使用する場合は、そのブリッジのためのコードを書くことになります。後でデータベース固有の ADO.NET データ プロバイダーが使用可能になったとき、このコードを書き直す必要があります。つまり、オブジェクト名、スキーマ情報、エラー処理、およびパラメーターを書き直さなければなりません。ブリッジ用ではなく管理データ プロバイダー用にコード化することによって、貴重な時間とリソースを節約できます。
|