CLR 統合のパフォーマンス

このトピックでは、Microsoft SQL Server を Microsoft .NET Framework CLR (共通言語ランタイム) に統合するときのパフォーマンスを強化するための設計の選択肢について説明します。

コンパイル処理

SQL 式のコンパイル時に、マネージ ルーチンへの参照が検出されると、MSIL (Microsoft Intermediate Language) スタブが生成されます。 このスタブには、ルーチン パラメーターを SQL Server から CLR にマーシャリングして関数を呼び出し、結果を返すコードが含まれています。 この "グルー" (接着剤) コードは、パラメーターの型とパラメーターの方向 (入力、出力、または参照) に基づいています。

グルー コードを使用することで、型固有の最適化を行い、NULL 値の許容、制約ファセット、値渡し、標準の例外処理などの SQL Server セマンティクスを効率的に適用できます。 引数に真数型を使用するコードを生成することで、複数の呼び出しにまたがる型の強制またはラッパー オブジェクト作成によるコストを回避 ("ボックス化") できます。

生成されたスタブは、CLR の JIT (just-in-time) コンパイル サービスによって、ネイティブ コードへのコンパイル、および SQL Server を実行している特定のハードウェア アーキテクチャに合わせた最適化が行われます。 JIT サービスはメソッド レベルで呼び出され、SQL Server と CLR 両方を実行するための単一のコンパイル単位を SQL Server ホスティング環境で作成できます。 スタブをコンパイルすると、コンパイルされた関数のポインターが関数の実行時の実装になります。 このコード生成方式によって、実行時にリフレクションやメタデータのアクセスのために追加の呼び出しを行うコストを回避することができます。

SQL Server と CLR の高速切り替え

コンパイル処理の結果、実行時にネイティブ コードから呼び出すことのできる関数ポインターが生成されます。 ユーザー定義スカラー値関数の場合、関数が行ごとに呼び出されます。 SQL Server と CLR の切り替えコストを最小限にするために、マネージ呼び出しを行うステートメントには対象になるアプリケーション ドメインを識別する起動処理があります。 この識別処理により、行ごとの切り替えコストを抑えます。

パフォーマンスに関する考慮事項

次に、SQL Server の CLR 統合固有のパフォーマンスに関する考慮事項を要約します。 詳細については、MSDN Web サイトの「SQL Server 2005 における CLR 統合の使用」を参照してください。 マネージ コードのパフォーマンスの詳細については、MSDN Web サイトの「.NET アプリケーションのパフォーマンスとスケーラビリティの向上」を参照してください。

ユーザー定義関数

CLR 関数は、Transact-SQL のユーザー定義関数に比べて呼び出し手順が速いという利点があります。 また、マネージ コードはプロシージャ コード、計算、および文字列操作のパフォーマンスが Transact-SQL に比べて決定的に優れています。 計算中心の CLR 関数およびデータ アクセスを行わない CLR 関数は、マネージ コードで記述する方が適切です。 ただし、データ アクセスは Transact-SQL 関数の方が CLR 統合に比べて効率的です。

ユーザー定義集計

マネージ コードを使用すると、カーソル ベースの集計よりも大幅に優れたパフォーマンスを発揮できます。 一般的には、組み込みの SQL Server 集計関数に比べてマネージ コードはわずかに低速です。 ネイティブの組み込み集計関数が存在する場合は、その関数を使用することをお勧めします。 必要な集計がネイティブにサポートされていない場合、パフォーマンス上の理由からカーソル ベースの実装よりも CLR ユーザー定義集計の使用を検討してください。

テーブル値関数のストリーミング

関数を呼び出した結果として、テーブルを返す必要性が生じる場合がよくあります。 たとえば、インポート操作の一環としてファイルから表形式のデータを読み取る場合や、コンマ区切りの値をリレーショナル表現に変換する場合などです。 一般的に、このような作業を実現するには、テーブルを呼び出し元で使用する前に、結果テーブルを具体化して値を格納する必要があります。 CLR を SQL Server に統合することで、STVF (ストリーミング テーブル値関数) という新しい拡張方式を使用できます。 マネージ STVF は、同様の拡張ストアド プロシージャを実装した場合に比べて、優れたパフォーマンスを発揮します。

STVF は、IEnumerable インターフェイスを返すマネージ関数です。 IEnumerable には STVF が返した結果セットの中を移動するメソッドがあります。 STVF を呼び出して返される IEnumerable は、クエリ プランに直接接続されます。 クエリ プランで行のフェッチが必要になると、IEnumerable のメソッドが呼び出されます。 このような反復的なモデルにより、テーブル全体に値が格納されるまで待たなくても、最初の行が生成された直後から結果を使用できます。 関数の呼び出しに伴うメモリの消費を大幅に抑えることもできます。

配列とカーソル

配列として簡単に表現できるデータを Transact-SQL カーソルでスキャンする必要がある場合、マネージ コードを使用するとパフォーマンスが大幅に向上します。

文字列データ

varchar などの SQL Server 文字データは、マネージ関数では SqlString 型または SqlChars 型にすることができます。 SqlString 変数は値全体のインスタンスをメモリに作成します。 SqlChars 変数には、ストリーミング インターフェイスが用意されており、これを使用すると、値全体のインスタンスをメモリに作成しないことでパフォーマンスおよびスケーラビリティを高めることができます。 このことは、特に LOB (ラージ オブジェクト) データにとって重要です。 また、SqlXml.CreateReader() が返すストリーミング インターフェイスを経由すると、サーバーの XML データにアクセスできます。

CLR と拡張ストアド プロシージャ

マネージ プロシージャから結果セットをクライアントに返す Microsoft.SqlServer.Server API (アプリケーション プログラミング インターフェイス) は、拡張ストアド プロシージャにより使用される ODS (オープン データ サービス) API に比べパフォーマンスに優れています。 また、System.Data.SqlServer API は SQL Server 2005 で導入された xml、varchar(max)、nvarchar(max)、varbinary(max) などのデータ型をサポートしていますが、ODS API ではこれらの新しいデータ型をサポートするための拡張が行われていません。

SQL Server ではマネージ コードによってメモリ、スレッド、同期などのリソースの使用状況が管理されます。 これらのリソースを公開するマネージ API が、SQL Server リソース マネージャーの上位に実装されるためです。 逆に、拡張ストアド プロシージャは SQL Server によってリソースの使用状況が監視または制御されることがありません。 たとえば、拡張ストアド プロシージャで大量の CPU リソースまたはメモリ リソースが消費されていても、それを SQL Server で検出したり制御することはできません。 一方、マネージ コードでは、特定のスレッドが長期間リソースを占有していることを SQL Server で検出して、タスクからリソースを解放し、他の作業のスケジュールを設定できるようになります。 つまり、マネージ コードを使用すると、スケーラビリティやシステム リソースの使用状況が改善されます。

マネージ コードを使用すると、実行環境の保持およびセキュリティ チェックの実施に必要なオーバーヘッドが発生することがあります。 たとえば、(SQL Server はネイティブ コードと行き来する際にスレッド固有の設定を保つ必要があるので、) SQL Server 内でコードを実行し、マネージ コードとネイティブ コードの切り替えを何度も行う必要がある場合などにオーバーヘッドが生じます。 つまり、マネージ コードとネイティブ コードの切り替えが頻発する場合は、SQL Server 内で実行されるマネージ コードに比べて、拡張ストアド プロシージャの方が高いパフォーマンスを発揮できます。

注意

この機能の使用は推奨されないため、拡張ストアド プロシージャを新規作成しないことをお勧めします。

ユーザー定義型のネイティブ シリアル化

UDT (ユーザー定義型) は、スカラー型システムの拡張方式として設計されています。 SQL Server には Format.Native という UDT のシリアル化形式が実装されています。 コンパイルのとき、型の定義に合わせてカスタマイズされた MSIL を生成するために型の構造を検査します。

ネイティブ シリアル化は SQL Server の既定の実装です。 ユーザー定義のシリアル化を行うと、型の作成者がシリアル化のために定義したメソッドが呼び出されます。 最高のパフォーマンスを得るには、Format.Native シリアル化をできる限り使用してください。

同等の UDT の正規化

UDT の並べ替え、比較などのリレーショナル操作で、値のバイナリ表現を直接操作します。 これを行うには、ディスクに UDT の状態を正規化した (バイナリ順にした) 表現を格納します。

正規化には 2 つの利点があります。1 つは、型のインスタンスの作成やメソッド呼び出しのオーバヘッドが発生しないようにすることで比較操作のコストが大幅に抑えられることです。もう 1 つは、UDT のバイナリ領域が作成され、ヒストグラム、インデックス、およびその型の値のヒストグラムが作成できるようになることです。 つまり、メソッド呼び出しを伴わない操作では、正規化した UDT はネイティブの組み込み型と変わらないパフォーマンスを発揮します。

スケーラビリティを確保するメモリの使用方法

SQL Server のマネージ ガベージ コレクションのパフォーマンスやスケーラビリティを高めるには、大量のメモリを 1 単位として割り当てないようにしてください。 88 KB を超える割り当てはラージ オブジェクト ヒープに配置されます。その結果、小規模の割り当てをいくつも行った場合に比べて、ガベージ コレクションのパフォーマンスやスケーラビリティが低下します。 たとえば、大きな多次元配列を割り当てる場合、ジャグ (散在した) 配列を割り当てることをお勧めします。

関連項目

概念

CLR ユーザー定義型