CLR 整合架構 - CLR 託管環境
適用於:SQL Server Azure SQL 受控執行個體
SQL Server 與 .NET Framework Common Language Runtime (CLR) 整合可讓資料庫程式設計人員使用 Visual C#、Visual Basic .NET 和 Visual C++ 等語言。 函式、預存程式、觸發程式、數據類型和匯總是程式設計人員可以使用這些語言撰寫的商業規則類型之一。
CLR 具有垃圾收集的記憶體、先佔式線程、元數據服務(類型反映)、程式代碼可驗證性,以及程式代碼存取安全性。 CLR 會使用元數據來尋找和載入類別、在記憶體中配置實例、解析方法調用、產生機器碼、強制執行安全性,以及設定運行時間內容界限。
CLR 和 SQL Server 會以處理記憶體、線程和同步處理的方式,與運行時間環境不同。 本文說明這兩個運行時間整合的方式,以便統一管理所有系統資源。 本文也涵蓋 CLR 程式代碼存取安全性 (CAS) 和 SQL Server 安全性整合的方式,以提供可靠且安全的使用者程式代碼執行環境。
CLR 架構的基本概念
在 .NET Framework 中,程式設計人員會以高階語言撰寫,以實作定義其結構的類別(例如,類別的字段或屬性)和方法。 其中一些方法可以是靜態函式。 程式的編譯會產生稱為元件的檔案,其中包含Microsoft中繼語言 (MSIL) 中已編譯的程式代碼,以及包含相依元件之所有參考的指令清單。
注意
元件是 CLR 架構中的重要元素。 它們是 .NET Framework 中應用程式程式代碼封裝、部署和版本設定的單位。 使用元件,您可以在資料庫內部署應用程式程式代碼,並提供統一的方式來管理、備份和還原完整的資料庫應用程式。
元件指令清單包含元件的相關元數據,描述程式中定義的所有結構、欄位、屬性、類別、繼承關聯性、函式和方法。 指令清單會建立元件識別、指定組成元件實作的檔案、指定組成元件的類型和資源、將編譯時間相依性逐項化為其他元件,以及指定元件正確執行所需的許可權集合。 這項資訊會在運行時間用來解析參考、強制執行版本系結原則,以及驗證載入元件的完整性。
.NET Framework 支援自定義屬性,以批注類別、屬性、函式和方法,以及應用程式可在元數據中擷取的其他資訊。 所有 .NET Framework 編譯程式都會取用這些批註,而不需要解譯,並將它們儲存為元件元數據。 您可以使用與任何其他元數據相同的方式來檢查這些批注。
Managed 程式代碼是在 CLR 中執行的 MSIL,而不是由作業系統直接執行。 Managed 程式代碼應用程式會取得 CLR 服務,例如自動垃圾收集、運行時間類型檢查和安全性支援。 這些服務有助於提供Managed程式碼應用程式的統一平臺和語言獨立行為。
CLR 整合的設計目標
當使用者程式代碼在 SQL Server 的 CLR 裝載環境中執行時,適用下列設計目標:
可靠性(安全)
用戶程式代碼不應被允許執行會危害 資料庫引擎 程式完整性的作業,例如彈出要求使用者回應或結束進程的消息框。 用戶程式代碼應該無法覆寫 資料庫引擎 記憶體緩衝區或內部數據結構。
延展性
SQL Server 和 CLR 有不同的內部模型,可用於排程和記憶體管理。 SQL Server 支援合作式的非先佔式線程模型,讓線程自願定期執行,或在等候鎖定或 I/O 時產生執行。 CLR 支援先佔式線程模型。 如果在 SQL Server 內執行的用戶程式代碼可以直接呼叫作業系統線程基本類型,則它不會很好地整合到 SQL Server 工作排程器中,而且可能會降低系統的延展性。 CLR 不會區分虛擬和實體記憶體,但 SQL Server 會直接管理物理記憶體,而且必須在可設定的限制內使用物理記憶體。
線程、排程和記憶體管理的不同模型,對於可調整以支援數千個並行使用者會話的關係資料庫管理系統(RDBMS)而言,提出了整合挑戰。 架構應該確保系統延展性不會受到呼叫應用程式程序設計介面的用戶程式代碼直接入侵,而無法直接進行線程、記憶體和同步處理基本類型。
安全性
存取數據表和數據行等資料庫物件時,在資料庫中執行的使用者程式代碼必須遵循 SQL Server 驗證和授權規則。 此外,資料庫管理員應該能夠從資料庫中執行的用戶程式代碼控制操作系統資源的存取,例如檔案和網路存取。 這種做法變得很重要,因為 Managed 程式設計語言(與 Transact-SQL 這類非受控語言不同)提供 API 來存取這類資源。 系統必須提供安全的方式,用戶程式代碼才能存取 資料庫引擎 進程外部的計算機資源。 如需相關資訊,請參閱 CLR Integration Security。
效能
在 資料庫引擎 中執行的 Managed 使用者程式代碼應該具有與伺服器外部執行相同程式代碼類似的計算效能。 來自受控使用者程式代碼的數據庫存取速度不如原生 Transact-SQL 那麼快。 如需詳細資訊,請參閱 CLR 整合的效能。
CLR 服務
CLR 提供一些服務,以協助達成 CLR 與 SQL Server 整合的設計目標。
類型安全驗證
型別安全程式代碼是只以定義完善的方式存取記憶體結構的程序代碼。 例如,假設有有效的對象參考,型別安全程式代碼可以在對應到實際欄位成員的固定位移存取記憶體。 不過,如果程式代碼在屬於對象之內存範圍內的任意位移存取記憶體,則不是類型安全。 當元件在 CLR 中載入時,在使用 Just-In-Time 編譯編譯 MSIL 之前,運行時間會執行驗證階段,以檢查程式代碼以判斷其類型安全性。 成功通過此驗證的程式代碼稱為可驗證的類型安全程序代碼。
應用程式網域
CLR 支援應用程式域的概念,做為主機進程內可以載入和執行 Managed 程式代碼元件的執行區域。 應用程式域界限提供元件之間的隔離。 元件會以靜態變數和數據成員的可見度,以及動態呼叫程式代碼的能力來隔離。 應用程式域也是載入和卸除程式代碼的機制。 程式代碼只能藉由卸除應用程式域,從記憶體卸除。 如需詳細資訊,請參閱 應用程式域和 CLR 整合安全性。
程式碼存取安全性 (CAS)
CLR 安全性系統提供一種方式,透過將許可權指派給程式代碼,來控制 Managed 程式代碼可執行的作業類型。 程式代碼訪問許可權是根據程式代碼識別指派的(例如,元件的簽章或程式碼的來源)。
CLR 提供可由計算機系統管理員設定的全計算機原則。 此原則會定義電腦上執行之任何 Managed 程式代碼的許可權授與。 此外,還有主機層級的安全策略可供 SQL Server 等主機使用,以指定 Managed 程式代碼的其他限制。
如果 .NET Framework 中的受控 API 公開受程式代碼訪問許可權保護之資源上的作業,則 API 會在存取資源之前要求該許可權。 這項需求會導致 CLR 安全性系統觸發呼叫堆疊中每個程式代碼單位(元件)的完整檢查。 只有在整個呼叫鏈結具有許可權時,才會授與資源的存取權。
請注意,在 SQL Server 中 CLR 裝載的環境中,不支援使用 Reflection.Emit API 動態產生 Managed 程式代碼的能力。 這類程式代碼不會有 CAS 許可權可執行,因此在運行時間會失敗。 如需詳細資訊,請參閱 CLR 整合程式代碼存取安全性。
主機保護屬性 (HPA)
CLR 提供一種機制,以批注 Managed API,這些 API 是 .NET Framework 的一部分,其中包含某些可能感興趣的 CLR 主機屬性。 這類屬性的範例包括:
SharedState,指出 API 是否公開建立或管理共享狀態的能力(例如靜態類別欄位)。
同步處理,指出 API 是否公開在線程之間執行同步處理的能力。
ExternalProcessMgmt,指出 API 是否公開控制主機進程的方式。
假設有這些屬性,主機可以指定應該不允許在託管環境中使用的 HPA 清單,例如 SharedState 屬性。 在此情況下,CLR 會拒絕使用者程式代碼嘗試呼叫禁止清單中 HPA 標註的 API。 如需詳細資訊,請參閱 主機保護屬性和 CLR 整合程序設計。
SQL Server 和 CLR 如何一起運作
本節討論 SQL Server 如何整合 SQL Server 和 CLR 的線程、排程、同步處理和記憶體管理模型。 特別是,本節會根據延展性、可靠性和安全性目標來檢查整合。 當 SQL Server 裝載於 SQL Server 內時,SQL Server 基本上會做為 CLR 的操作系統。 CLR 會呼叫 SQL Server 針對線程、排程、同步處理和記憶體管理所實作的低階例程。 這些例程是 SQL Server 引擎其餘部分所使用的相同基本類型。 此方法提供數個延展性、可靠性和安全性優點。
延展性:常見的線程、排程和同步處理
CLR 會呼叫 SQL Server API 來建立線程,無論是用於執行使用者程式代碼,還是用於自己的內部使用。 為了在多個線程之間同步處理,CLR 會呼叫 SQL Server 同步處理物件。 這種做法可讓 SQL Server 排程器在線程等候同步處理物件時排程其他工作。 例如,當 CLR 起始垃圾收集時,其所有線程都會等候垃圾收集完成。 由於 SQL Server 排程器已知 CLR 線程和它們正在等候的同步處理物件,所以 SQL Server 可以排程執行其他不涉及 CLR 之資料庫工作的線程。 這也可讓 SQL Server 偵測涉及 CLR 同步處理對象所採取鎖定的死結,並採用傳統技術來移除死結。
受控程式代碼會在 SQL Server 中先佔執行。 SQL Server 排程器能夠偵測和停止尚未產生大量時間的線程。 將 CLR 線程連結至 SQL Server 線程的能力表示 SQL Server 排程器可以識別 CLR 中的「失控」線程,並管理其優先順序。 這類失控線程會暫停並放回佇列中。 重複識別為失控線程的線程不允許在指定時間內執行,讓其他執行中的背景工作角色可以執行。
在某些情況下,長時間執行的Managed程式代碼會自動產生,有些情況則不會。 在下列情況下,長時間執行的Managed程式碼將會自動產生:
- 如果程式代碼呼叫 SQL OS (例如查詢資料)
- 如果配置足夠的記憶體來觸發垃圾收集
- 如果程式代碼藉由呼叫OS函式進入先占模式
不執行上述任何動作的程序代碼,例如只包含計算的緊密迴圈,不會自動產生排程器,這可能會導致長時間等候系統中的其他工作負載。 在這些情況下,開發人員必須藉由呼叫 .NET Framework 的 System.Thread.Sleep() 函式,或在預期長時間執行的程式代碼區段中明確進入預先模擬模式來明確產生。 下列程式代碼範例示範如何使用這些方法手動產生。
// 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 中的 Managed 程式代碼遇到重大例外狀況,例如記憶體不足或堆棧溢位時,不一定能夠從這類失敗中復原,並確保其實作的一致且正確的語意。 這些 API 會引發線程中止例外狀況,以回應這些失敗。
在 SQL Server 中裝載時,這類線程中止的處理方式如下:CLR 會偵測發生線程中止的應用程式域中的任何共享狀態。 CLR 會藉由檢查同步處理物件是否存在來偵測此情況。 如果應用程式域中有共享狀態,則會卸除應用程式域本身。 卸除應用程式域會停止目前正在該應用程式域中執行的資料庫交易。 由於共享狀態的存在可能會將這類重大例外狀況的影響擴大至觸發例外狀況的用戶會話,因此 SQL Server 和 CLR 已採取措施,以減少共享狀態的可能性。 如需詳細資訊,請參閱 .NET Framework 檔。
安全性:許可權集合
SQL Server 可讓使用者指定部署至資料庫之程式代碼的可靠性和安全性需求。 將元件上傳至資料庫時,元件的作者可以為該元件指定三個許可權集合之一:SAFE、EXTERNAL_ACCESS和 UNSAFE。
功能 | SAFE | EXTERNAL_ACCESS | UNSAFE |
---|---|---|---|
程式碼存取安全性 | 僅限執行 | 執行 + 存取外部資源 | 不受限制 |
程式設計模型限制 | Yes | Yes | 無限制 |
可驗證性需求 | Yes | 是 | No |
呼叫原生程式碼的能力 | No | 無 | Yes |
SAFE 是最可靠且安全的模式,並且在允許的程式設計模型方面有相關限制。 SAFE 組件有足夠的權限來執行、執行計算,以及存取本機資料庫。 SAFE 組件必須是可驗證的型別安全,且不允許呼叫 Unmanaged 程式碼。
UNSAFE 適用於只能由資料庫管理員建立的高度信任程式碼。 此信任的程式代碼沒有程式代碼存取安全性限制,而且可以呼叫 Unmanaged (原生) 程式代碼。
EXTERNAL_ACCESS提供中繼安全性選項,允許程式代碼存取資料庫外部的資源,但仍具有SAFE的可靠性保證。
SQL Server 會使用主機層級 CAS 原則層來設定主機原則,根據儲存在 SQL Server 目錄中的許可權集,授與三組許可權的其中一個。 在資料庫內執行的 Managed 程式碼一律會取得這些程式碼存取權限集合的其中一個。
程式設計模型限制
SQL Server 中 Managed 程式代碼的程式設計模型牽涉到撰寫函式、程式和類型,這些函式、程式和類型通常不需要在多個調用之間使用狀態,或跨多個使用者會話共享狀態。 此外,如先前所述,共用狀態的存在可能會導致嚴重的例外狀況,影響應用程式的延展性和可靠性。
考慮到這些考慮,我們不建議使用 SQL Server 中所使用類別的靜態變數和靜態數據成員。 針對 SAFE 和 EXTERNAL_ACCESS 元件,SQL Server 會在 CREATE ASSEMBLY 時間檢查元件的元數據,如果找到靜態數據成員和變數的使用,則無法建立這類元件。
SQL Server 也不允許呼叫以 SharedState、Synchronization 和 ExternalProcessMgmt 主機保護屬性標註的 .NET Framework API。 這可防止 SAFE 和 EXTERNAL_ACCESS元件呼叫任何啟用共享狀態、執行同步處理,以及影響 SQL Server 進程完整性的 API。 如需詳細資訊,請參閱 CLR 整合程式設計模型限制。