CLR 統合のアーキテクチャ - CLR ホスト環境

適用対象:SQL ServerAzure SQL Managed Instance

SQL Server共通言語ランタイム (CLR) .NET Frameworkとの統合により、データベース プログラマは Visual C#、Visual Basic .NET、Visual C++ などの言語を使用できます。 プログラマがこれらの言語を使用して記述できるビジネス ロジックには、関数、ストアド プロシージャ、トリガー、データ型、集計などがあります。

CLR には、ガベージ コレクション メモリ、プリエンプティブ スレッド、メタデータ サービス (型リフレクション)、コード検証可能性、コード アクセス セキュリティが備わります。 CLR では、クラスの検索と読み込み、メモリ内でのインスタンスのレイアウト、メソッド呼び出しの解決、ネイティブ コードの生成、セキュリティの設定、およびランタイム コンテキスト境界の設定にメタデータが使用されます。

CLR とSQL Serverは、メモリ、スレッド、同期の処理方法でランタイム環境と異なります。 この記事では、すべてのシステム リソースが一様に管理されるように、これら 2 つの実行時間を統合する方法について説明します。 この記事では、CLR コード アクセス セキュリティ (CAS) とSQL Server セキュリティを統合して、ユーザー コードの信頼性とセキュリティで保護された実行環境を提供する方法についても説明します。

CLR アーキテクチャの基本概念

.NET Framework では、プログラマは高級言語を使ってクラスを実装し、そのクラスの構造 (クラスのフィールドやプロパティなど) とメソッドを定義します。 このようなメソッドの一部は、静的関数にすることができます。 プログラムのコンパイルでは、Microsoft 中間言語 (MSIL) のコンパイル済みコードを含むアセンブリと呼ばれるファイルと、依存アセンブリへのすべての参照を含むマニフェストが生成されます。

注意

アセンブリは CLR アーキテクチャの不可欠な要素です。 アセンブリは、.NET Framework のアプリケーション コードのパッケージ化、配置、およびバージョン管理の単位になります。 アセンブリを使用して、データベース内部にアプリケーション コードを配置し、完全なデータベース アプリケーションの管理、バックアップ、および復元を行うための統一された方法を提供できます。

アセンブリ マニフェストには、アセンブリに関するメタデータが含まれており、プログラムで定義されているすべての構造体、フィールド、プロパティ、クラス、継承関係、関数、およびメソッドの情報が記述されています。 マニフェストでは、アセンブリ ID の確立、アセンブリの実装を構成するファイルの指定、アセンブリを構成する型やリソースの指定、他のアセンブリに対するコンパイル時の依存関係の列挙、およびアセンブリを正しく実行するために必要な権限セットの指定を行います。 この情報を実行時に使用して、参照の解決、バージョン バインド ポリシーの設定、および読み込まれたアセンブリの整合性の検証が行われます。

.NET Framework では、アプリケーションがメタデータでキャプチャできる追加情報により、クラス、プロパティ、関数、およびメソッドに注釈を付けるためのカスタム属性がサポートされます。 すべての .NET Framework コンパイラでは、このような注釈を解釈せずに使用し、アセンブリ メタデータとして格納します。 このような注釈は、他のメタデータと同じ方法で調べることができます。

マネージド コードは、オペレーティング システムが直接実行するのではなく、CLR で MSIL として実行されます。 マネージド コード アプリケーションは、自動ガベージ コレクション、ランタイム型チェック、セキュリティ サポートなどの CLR サービスを使用します。 これらのサービスは、プラットフォームや言語に依存しない統一的な動作を、マネージド コード アプリケーションに提供するのに役立ちます。

CLR 統合の設計目標

SQL Server (CLR 統合と呼ばれます) で CLR でホストされる環境内でユーザー コードを実行する場合、次の設計目標が適用されます。

信頼性 (安全性)

ユーザーからの応答を要求するメッセージ ボックスの表示やプロセスの終了など、データベース エンジン プロセスの整合性に影響を与える操作は、ユーザー コードから実行できないようにする必要があります。 ユーザー コードからデータベース エンジンのメモリ バッファーや内部データ構造体への上書きを許可しないでください。

スケーラビリティ

SQL Serverと CLR には、スケジュールとメモリ管理のための異なる内部モデルがあります。 SQL Serverでは、スレッドが定期的に、またはロックまたは I/O を待機しているときに、スレッドが自発的に実行を生成する、協調的でプリエンプティブでないスレッド モデルがサポートされています。 CLR では、プリエンプティブなスレッド モデルがサポートされます。 SQL Server内で実行されているユーザー コードがオペレーティング システムスレッドプリミティブを直接呼び出すことができる場合、SQL Serverタスク スケジューラにうまく統合されず、システムのスケーラビリティが低下する可能性があります。 CLR は仮想メモリと物理メモリを区別しませんが、SQL Serverは物理メモリを直接管理し、構成可能な制限内で物理メモリを使用する必要があります。

このようにスレッド処理、スケジュール設定、およびメモリ管理のモデルが異なるため、数千の同時実行ユーザー セッションをサポートするまで規模が拡大された RDBMS (リレーショナル データベース管理システム) では統合が課題になります。 アーキテクチャでは、スレッド処理、メモリ、および同期プリミティブの API (アプリケーション プログラミング インターフェイス) を直接呼び出すユーザー コードによってシステムのスケーラビリティが損なわれないことを保証する必要があります。

セキュリティ

データベースで実行されているユーザー コードは、テーブルや列などのデータベース オブジェクトにアクセスするときに、SQL Server認証と承認の規則に従う必要があります。 また、データベース管理者は、データベースで実行しているユーザー コードからファイルやネットワーク アクセスなどのオペレーティング システム リソースへのアクセスを制御できる必要があります。 (Transact-SQL などの非管理言語とは異なり) マネージ プログラミング言語がこのようなリソースにアクセスするための API を提供するため、この方法が重要になります。 システムは、ユーザー コードがデータベース エンジン プロセス外のマシン リソースにアクセスするための安全な方法を提供する必要があります。 詳細については、「 CLR 統合のセキュリティ」を参照してください。

パフォーマンス

データベース エンジンで実行されているマネージド ユーザー コードの計算パフォーマンスは、サーバーの外部で実行されるのと同じコードと同等である必要があります。 マネージド ユーザー コードからのデータベース アクセスは、ネイティブ Transact-SQL ほど高速ではありません。 詳細については、「 CLR 統合のパフォーマンス」を参照してください。

CLR サービス

CLR には、CLR とSQL Serverの統合の設計目標を達成するのに役立つさまざまなサービスが用意されています。

型の安全性の検証

タイプ セーフなコードとは、メモリ構造にアクセスする際に適切に定義された方法のみを使用するコードのことです。 たとえば、有効なオブジェクト参照を例として考えると、タイプ セーフなコードでは、実際のフィールド メンバーに対応してメモリの固定オフセット位置にアクセスできます。 一方、オブジェクトに属するメモリの範囲の内外を問わず、任意のオフセット位置でメモリにアクセスするコードは、タイプ セーフではありません。 アセンブリを CLR に読み込むと、JIT (Just-In-Time) コンパイルを使用して MSIL にコンパイルされる前に、ランタイムによって、コードのタイプ セーフティを判断するためにそのコードを調べる検証フェーズが実行されます。 この検証に正常に合格するコードを、検証可能なタイプ セーフなコードと呼びます。

アプリケーション ドメイン

CLR では、マネージド コード アセンブリを読み込み、実行できるホスト プロセス内の実行領域として、アプリケーション ドメインの概念がサポートされます。 アプリケーション ドメインの境界でアセンブリどうしが分離されます。 アセンブリは、静的変数やデータ メンバーの可視性、およびコードを動的に呼び出す機能に関して分離されます。 また、アプリケーション ドメインはコードのロードとアンロード用のメカニズムでもあります。 アプリケーション ドメインをアンロードしないと、コードをメモリからアンロードできません。 詳細については、「 アプリケーション ドメインと CLR 統合セキュリティ」を参照してください。

コード アクセス セキュリティ (CAS)

CLR セキュリティ システムには、マネージド コードに権限を割り当てて、そのコードで実行できる操作の種類を制御する方法が用意されています。 コード アクセス権限は、コード ID (アセンブリの署名やコードの作成元など) に基づいて割り当てられます。

CLR では、コンピューター管理者が設定できるコンピューター全体のポリシーが規定されます。 このポリシーでは、コンピューターで実行される任意のマネージド コードに許可される権限が定義されます。 さらに、マネージド コードに対する追加の制限を指定するために、SQL Server などのホストで使用できるホスト レベルのセキュリティ ポリシーがあります。

.NET Framework のマネージド API により、コード アクセス権限で保護されているリソースでの操作が公開される場合、そのリソースへのアクセスが行われる前に、API がそのアクセス権限を要求することになります。 この要求により、CLR セキュリティ システムが呼び出し履歴内のすべての単位のコード (アセンブリ) を包括的にチェックします。 リソースへのアクセスは、呼び出しチェーン全体にアクセス許可がある場合にのみ付与されます。

Reflection.Emit API を使用してマネージド コードを動的に生成する機能は、SQL Serverの CLR でホストされる環境内ではサポートされていないことに注意してください。 このようなコードには実行するための CAS 権限がないので、コードは実行時に失敗します。 詳細については、「 CLR 統合コード アクセス セキュリティ」を参照してください。

HPA (ホスト保護属性)

CLR には、.NET Framework の一部であるマネージド API に、特定の属性で注釈を付けるメカニズムが用意されています。このような属性は CLR のホストにとって意味のある属性です。 次に、このような属性の例を示します。

  • SharedState。共有状態 (静的なクラス フィールドなど) を作成または管理する機能が API で公開されるかどうかを示します。

  • Synchronization。スレッド間で同期を実行する機能が API で公開されるかどうかを示します。

  • ExternalProcessMgmt。ホスト プロセスを制御する方法が API で公開されるかどうかを示します。

これらの属性を例として考えると、ホストされている環境で禁止される必要がある SharedState 属性などの HPA の一覧をホストで指定できます。 この場合、CLR では禁止一覧の HPA で注釈が付けられている API がユーザー コードから呼び出されることを拒否します。 詳細については、「 ホスト保護属性」と「CLR 統合プログラミング」を参照してください。

SQL Server と CLR の連携方法

このセクションでは、SQL ServerがSQL Serverと CLR のスレッド、スケジュール、同期、およびメモリ管理モデルを統合する方法について説明します。 特に、スケーラビリティ、信頼性、およびセキュリティの目標に照らし合わせて、この統合について解説します。 SQL Server基本的には、CLR がSQL Server内でホストされている場合に、CLR のオペレーティング システムとして機能します。 CLR は、スレッド処理、スケジュール設定、同期、メモリ管理のために、SQL Serverによって実装された低レベルのルーチンを呼び出します。 これらのルーチンは、SQL Server エンジンの残りの部分で使用されるのと同じプリミティブです。 このアプローチを使用すると、スケーラビリティ、信頼性、およびセキュリティに関するいくつかの利点が得られます。

スケーラビリティ : 一般的なスレッド処理、スケジュール設定、および同期

CLR はSQL Server API を呼び出して、ユーザー コードの実行と独自の内部使用の両方のスレッドを作成します。 複数のスレッド間で同期するために、CLR は同期オブジェクトSQL Server呼び出します。 この方法により、SQL Server スケジューラは、スレッドが同期オブジェクトで待機しているときに他のタスクをスケジュールできます。 たとえば、CLR からガベージ コレクションが開始されると、CLR のすべてのスレッドがガベージ コレクションの終了を待機します。 待機している CLR スレッドと同期オブジェクトは、SQL Server スケジューラに認識されるため、SQL Serverは、CLR を含まない他のデータベース タスクを実行しているスレッドをスケジュールできます。 これにより、SQL Serverは CLR 同期オブジェクトによって取得されたロックを含むデッドロックを検出し、デッドロックの除去に従来の手法を使用することもできます。

マネージド コードは、SQL Serverでプリエンプティブに実行されます。 SQL Server スケジューラには、長時間生成されていないスレッドを検出および停止する機能があります。 CLR スレッドをSQL Serverスレッドにフックする機能は、SQL Server スケジューラが CLR 内の "暴走" スレッドを識別し、その優先順位を管理できることを意味します。 このようなランナウェイ スレッドは中断され、キューに戻されます。 繰り返しランナウェイ スレッドとして識別されたスレッドは、実行中の他のワーカーを実行できるように、一定期間実行が許可されません。

実行時間の長いマネージド コードが自動的に生成される状況もあれば、生成されない状況もあります。 次の状況では、実行時間の長いマネージド コードが自動的に生成されます。

  • コードが SQL OS を呼び出す場合 (たとえば、データに対してクエリを実行する)
  • ガベージ コレクションをトリガーするのに十分なメモリが割り当てられている場合
  • OS 関数を呼び出してコードがプリエンプティブ モードに入った場合

上記のいずれも行わないコード (たとえば、計算のみを含むタイト なループなど) では、スケジューラが自動的に生成されないため、システム内の他のワークロードが長時間待機する可能性があります。 このような状況では、開発者は、.NET Frameworkの System.Thread.Sleep() 関数を呼び出すか、または実行時間が長いと予想されるコードの任意のセクションで System.Thread.BeginThreadAffinity() を使用してプリエンティブ モードに明示的に入ることで、明示的に生成します。 次のコード例は、これらの各メソッドを使用して手動で生成する方法を示しています。

// Example 1: Manually yield to SOS scheduler.
for (int i = 0; i < Int32.MaxValue; i++)
{
 // *Code that does compute-heavy operation, and does not call into
 // any OS functions.*

 // Manually yield to the scheduler regularly after every few cycles.
 if (i % 1000 == 0)
 {
   Thread.Sleep(0);
 }
}
// Example 2: Use ThreadAffinity to run preemptively.
// Within BeginThreadAffinity/EndThreadAffinity the CLR code runs in preemptive mode.
Thread.BeginThreadAffinity();
for (int i = 0; i < Int32.MaxValue; i++)
{
  // *Code that does compute-heavy operation, and does not call into
  // any OS functions.*
}
Thread.EndThreadAffinity();
スケーラビリティ : 一般的なメモリ管理

CLR はSQL Serverプリミティブを呼び出して、メモリの割り当てと割り当て解除を行います。 CLR によって使用されるメモリは、システムのメモリ使用量の合計で考慮されるため、SQL Serverは構成されたメモリ制限内に留まり、CLR とSQL Serverが相互にメモリと競合しないようにすることができます。 SQL Serverは、システム メモリが制約されている場合に CLR メモリ要求を拒否し、他のタスクでメモリが必要な場合に CLR にメモリ使用量を減らすように求めることもできます。

信頼性 : アプリケーション ドメインと回復できない例外

.NET Framework API のマネージド コードで、メモリ不足やスタック オーバーフローなどの重大な例外が発生した場合、必ずそのようなエラーから回復し、API の実装に対して一貫性のある正しいセマンティクスを保証できるとは限りません。 これらの API により、このようなエラーへの応答でスレッドを中断する例外が発生します。

SQL Serverでホストされている場合、このようなスレッドの中止は次のように処理されます。CLR は、スレッドの中止が発生したアプリケーション ドメイン内の共有状態を検出します。 CLR は、同期オブジェクトの存在を確認することによってこれを検出します。 アプリケーション ドメインに共有状態が存在する場合は、アプリケーション ドメイン自体がアンロードされます。 アプリケーション ドメインをアンロードすると、そのアプリケーション ドメインで現在実行されているデータベース トランザクションが停止します。 共有状態が存在すると、このような重大な例外が例外をトリガーするセッション以外のユーザー セッションへの影響を拡大する可能性があるため、SQL Serverと CLR は共有状態の可能性を減らす手順を実行しました。 詳細については、.NET Framework のドキュメントを参照してください。

セキュリティ : 権限セット

SQL Serverを使用すると、ユーザーはデータベースにデプロイされるコードの信頼性とセキュリティの要件を指定できます。 アセンブリがデータベースにアップロードされると、アセンブリの作成者は、そのアセンブリに対して SAFE、EXTERNAL_ACCESS、UNSAFE の 3 つの権限セットのいずれかを指定できます。

機能 SAFE EXTERNAL_ACCESS UNSAFE
コード アクセス セキュリティ 実行のみ 実行および外部リソースへのアクセス 無制限
プログラミング モデルの制限事項 はい はい 制限事項なし
検証可能性の要件 はい はい いいえ
ネイティブ コードを呼び出す機能 いいえ いいえ はい

SAFE は、許可されているプログラミング モデルの中でも多くの制限事項が関連付けられており、最も信頼性が高く、セキュリティで保護されたモードです。 SAFE アセンブリには、実行、計算の実行、およびローカル データベースへのアクセスを行うには十分な権限が許可されます。 SAFE アセンブリは検証可能なタイプ セーフである必要があり、アンマネージ コードを呼び出すことはできません。

UNSAFE は、データベース管理者のみが作成できる信頼性の高いコードに指定します。 この信頼性の高いコードにはコード アクセス セキュリティに関する制限がなく、アンマネージ (ネイティブ) コードを呼び出すことができます。

EXTERNAL_ACCESS には、両者の中間に位置するセキュリティ オプションが提供されます。このオプションにより、SAFE の信頼性保証を備えたまま、コードからデータベース外部のリソースにアクセスできます。

SQL Serverは、ホスト レベルの CAS ポリシー レイヤーを使用して、SQL Server カタログに格納されているアクセス許可セットに基づいて、3 つのアクセス許可セットのいずれかを許可するホスト ポリシーを設定します。 データベース内部で実行するマネージド コードには、これらのコード アクセス権限セットのうちのいずれかが必ず許可されます。

プログラミング モデルの制限

SQL Serverのマネージ コードのプログラミング モデルには、関数、プロシージャ、型の記述が含まれます。通常、複数の呼び出しで保持されている状態の使用や、複数のユーザー セッション間での状態の共有は必要ありません。 さらに、既に説明したように、共有状態が存在すると、アプリケーションのスケーラビリティや信頼性に影響を与える重大な例外が発生する可能性があります。

これらの考慮事項を考慮して、SQL Serverで使用されるクラスの静的変数と静的データ メンバーを使用しないことをお勧めします。 SAFE アセンブリと EXTERNAL_ACCESS アセンブリの場合、SQL Serverは CREATE ASSEMBLY 時にアセンブリのメタデータを調べ、静的データ メンバーと変数の使用が見つかると、そのようなアセンブリの作成に失敗します。

SQL Serverでは、SharedStateSynchronizationExternalProcessMgmt の各ホスト保護属性で注釈が付けられた.NET Framework API の呼び出しも禁止されます。 これにより、SAFE アセンブリとEXTERNAL_ACCESS アセンブリが、共有状態を有効にし、同期を実行し、SQL Server プロセスの整合性に影響を与える API を呼び出すのを防ぐことができます。 詳細については、「 CLR 統合プログラミング モデルの制限」を参照してください。

参照

CLR 統合のセキュリティ
CLR 統合のパフォーマンス