天天看點

Microsoft SQL Server 查詢處理器的内部機制與結構

轉自:http://database.51cto.com/art/200512/12908.htm

本文介紹了在客戶機上處理 Microsoft SQL Server 查詢的方式,各種客戶機與 SQL Server 的互動方式,以及 SQL Server 在處理客戶機程式的請求時需要完成的工作。

AD:

簡介

Microsoft(R) SQL Server(TM) 内部機制和結構是一個非常大的主題,是以本文僅限于程式開發人員感興趣的問題,集中研究其他源中沒有徹底讨論的問題。在讨論 SQL Server 的結構時,我們主要觀察客戶機的處理過程,研究不同的客戶機程式與 SQL Server 的互動方式,以及 SQL Server 如何處理客戶機的請求。還有一些讨論 SQL Server 其他方面的資訊源,特别是 Microsoft Press 出版的 Inside SQL Server 7.0,作者是 Ron Soukup 和 Kalen Delaney,這本書非常詳細地讨論了 SQL Server 存儲引擎的内部機制和處理方法,不過對查詢處理器的讨論不夠深入。本文正填補了這個空白。

我們期望本文有助于讀者編寫出更好的應用程式。通過本文,讀者會在提高程式性能方面得到新的啟發,産生新的了解。

SQL Server 是一種客戶機/伺服器系統

多年來,SQL Server 一直被認為是一種客戶機/伺服器系統。事實上,Sybase DataServer(以此為基礎開發了原始的 SQL Server)正是第一個作為客戶機/伺服器系統開發的商用關系資料庫系統。那這又說明了什麼呢?這不隻意味着 SQL Server 是一個雙層系統。從傳統上看,雙層系統意味着客戶機應用程式運作在一台機器上,向另一台計算機上的伺服器發送請求。而對于 SQL Server,客戶機/伺服器意味着 SQL Server 的組成部分,即客戶機 API 部分,駐留在處理結構中的遠端,與伺服器元件本身是分開的。

在典型的雙層模型中,客戶機程式部分駐留在桌上型電腦上,具有大量客戶機應用程式邏輯和業務邏輯,并且會直接向資料庫系統送出請求。然後,客戶機得到伺服器響應這些請求所傳回的資料。

三層系統也采用了同樣的模型。多年以來,SQL Server 一直用在事務處理監視系統中,例如 BEA 的 Tuxedo 以及 Compaq 的 ACMSxp,這些系統早在二、三十年前就采用了典型的三層模型。三層模型在今天基于 Web 的應用系統中占據了支配地位,這類系統以 Microsoft 的 MTS 以及新的 COM+ 1.0 為代表。從 SQL Server 的角度看,三層解決方案中的客戶機程式是放在中間層的。中間層直接與資料庫互動。實際的桌面,或瘦客戶機(Thin Client),使用其他機制并通常直接與中間層互動,而不是直接與資料庫系統互動。圖 1 描述了這種結構。

 

Microsoft SQL Server 查詢處理器的内部機制與結構

圖 1. 三層系統模型

客戶機結構

從結構的角度看,SQL Server 關系伺服器元件本身并不真正關心客戶機程式運作的位置。事實上,就 SQL Server 而言,即使在運作 SQL Server 的同一台機器上運作應用程式,仍然還是客戶機/伺服器模型。伺服器運作一個單獨的多線程程序,為來自客戶機的請求提供服務,不管客戶機的位置在哪裡。客戶機程式代碼本身是單獨的運作在客戶機應用程式内部的 DLL,與 SQL Server 的實際接口是在客戶機和伺服器之間對話的“表格資料流”(Tabular Data Stream, TDS) 協定。

一個常見的問題是“什麼是 SQL Server 的本機接口呢?”很長時間以來,很多開發人員一直都不願意使用 ODBC 這樣的接口,因為他們認為由 Sybase 開發的客戶機 API,也就是 DB-Library,是 SQL Server 的本機接口。實際上,SQL Server 關系伺服器本身并沒有本機 API,它的接口就是在客戶機和伺服器之間的通信流協定 TDS。TDS 把客戶機發送給伺服器的 SQL 語句封裝起來,也把伺服器傳回給客戶機的處理結果封裝起來。任何直接處理 TDS 的 API 都是 SQL Server 的本機接口。

讓我們來看一下客戶機的元件,如圖 2 所示。客戶機結構中的某些部分就不在這裡讨論了,因為它們不屬于 SQL Server 的範疇。但如果您在編寫應用程式的話,就必須了解這些部分。大家知道得最多的應該是各種對象模型,如果您正在編寫 ASP 或 Microsoft Visual Basic(R) 應用程式,就需要通過 ADO 與資料庫系統互動,而不是直接調用底層的 API,例如 ODBC 或 OLE-DB。ADO 映射到 OLE-DB,而 RDO 映射到 ODBC。是以,作為這種最常用的程式設計模型的對象模型,并不是 SQL Server 客戶機結構中的嚴格意義上的元件。此外,還有另外一些元件可以插接到 SQL Server 基礎結構上面的這一層。OLE-DB 的“會話池服務提供程式 (Session Pooling Service Provider)”就是這種元件的一個例子。

Microsoft SQL Server 查詢處理器的内部機制與結構

圖 2. 客戶機結構

 

客戶機接口

SQL Server 有兩個接口可以認為是 SQL Server 7.0 的本機接口,即 OLE-DB 和 ODBC。DB-Library 接口也是本機的,它與 TDS 通信,但是 DB-Library 使用的是 TDS 較老的版本,需要在伺服器上進行一些轉換。現有的 DB-Library 應用程式仍然可以繼續與 SQL Server 7.0 協同使用,但是很多新的功能和性能提高等好處隻能通過 ODBC 和 OLE DB 才能利用。更新 DB-Library 使其支援 SQL Server 7.0 的新能力,将會導緻與現有應用程式的很多不相容性,是以需要修改應用程式。ODBC 在五年之前就替代了 DB-Library,是新的 SQL Server 應用程式更理想的 API,是以引入不相容的 DB-Library 新版本并不明智。

從圖 2 可以看到,所有這些客戶機 API 都有三個部分。最上面的部分實作 API 的細節,例如行集和遊标應該是什麼樣等等。TDS 格式化程式負責處理實際請求,例如 SQL 語句,并将其封裝成 TDS 消息包,發送給 SQL Server,獲得傳回的結果,然後再把結果回報到接口實作。

還有一些供所有提供程式使用的公共庫代碼。例如,BCP 裝置就是 ODBC 和 OLE-DB 都可以調用的庫。DTC 也是這樣。第三個例子是 ODBC 規範的 SQL 文法,即帶有參數标記的 CALL 文法,這些對于所有提供程式都是通用的。

除了我們在前面已經提到的局限性,即 DB-Library 仍然隻能使用 SQL Server 6.5 版,TDS 協定對于所有 API 都是相同的。ODBC 和 OLE-DB 在與 SQL Server 7.0 通信時使用 SQL Server 7.0 版,但也能夠與 6.5 或 6.0 伺服器通信。另一個是 Net-Library,這是一個抽象層,客戶機和伺服器都在此層上同網絡抽象接口通信,不必為 IPX 還是 TCP/IP 困擾。在這裡我們将不讨論 Net-Library 的工作細節;隻要知道它們的工作基本上是将來自的網絡通信底層的細節隐藏起來不讓軟體的其他部分看到就可以了。

從客戶機的角度看伺服器

前面已經提到過,客戶機與 SQL Server 通信的主要方法就是通過使用 TDS 消息。TDS 是一種簡單協定。當 SQL Server 接收到一條消息時,可以認為是發生了一個事件。首先,客戶機在一個連接配接上發送登入消息(或事件),并得到傳回的成功或失敗的響應。當您希望發送 SQL 語句時,客戶機可以把 SQL 語言消息打包發送給 SQL Server。另外,當您希望調用存儲過程、系統過程或虛拟系統存儲過程(我們後面還要詳細讨論)時,客戶機可以發送 RPC 消息,這種消息相當于 SQL Server 上的一個 RPC 事件。對于上面的後兩種情況,伺服器會以資料令牌流的形式送回結果。Microsoft 沒有把實際的 TDS 消息寫入文檔中,因為這被認為是 SQL Server 元件之間的私用契約。

目錄存儲過程是另一類關鍵的客戶機/伺服器的互動部分。這些存儲過程首先在 ODBC 的 SQL Server 6.0 中出現, 包括諸如 sp_tables 和 sp_columns 等存儲過程。ODBC 和 OLE-DB API 定義了描述有關資料庫對象的中繼資料的标準方法,這些标準需要适用于所有類型的 RDBMS 伺服器,而不必調整為 SQL Server 自己的系統表。不是客戶機向伺服器發送對系統表的多個查詢,并在客戶機端建立标準的中繼資料視圖,而是建立一組存儲在伺服器上的系統存儲過程,并對 API 傳回适當格式的資訊。這種方法使得通過一次通信就可以完成很多重要的中繼資料請求。

為 ODBC 編寫的過程已經寫入文檔,通常适合需要從系統表中擷取資訊但其他機制沒有提供這種方法的情況。這使得 Transact-SQL 過程和 DB-Library 應用程式可以通路中繼資料,而不需要編寫對 SQL Server 系統表的複雜查詢,并且使應用程式不受今後 Microsoft 修改系統表的影響。

OLE DB 定義了一組架構行集,它們類似于 ODBC 的中繼資料,但又和它不同。它建立了一組新的目錄存儲過程,以更有效地為這些架構行集植入資料。但是,這組新的存儲過程沒有寫入文檔,因為這些存儲過程重複了早先提供的功能。通過現有的若幹種方法都可以得到中繼資料,是以 SQL Server 開發組決定不顯露這些并沒有為程式設計模型增加新内容的對象。

客戶機與伺服器的互動還有第三個方面。它最初出現在 SQL Server 6.0 中,但是沒有得到普遍使用。這就是虛拟系統存儲過程的概念;在 SQL Server 7.0 中起很重要的作用。當第一次為 SQL Server 6.0 開發伺服器端遊标時,開發人員就需要選擇采取什麼方法管理客戶機/伺服器的互動。遊标并不特别适合現有的 TDS 消息,因為這些消息允許逐行傳回資料,不需要客戶機指定額外的 SQL 語句。開發人員本來可以向 TDS 協定添加更多的消息,但是需要修改太多的其他元件。SQL Server 6.0 中的 TDS 版本還需要向 Sybase 版本靠攏,以便確定兩者的可互操作性,于是開發人員選擇了另外的處理機制。他們開發了外表看起來像是系統存儲過程的新功能(伺服器端遊标),實際上是指向 SQL Server 代碼的入口存儲過程。它們被客戶機應用程式使用标準的 RPC TDS 消息來調用。它們被稱為虛拟系統存儲過程,因為在客戶機上,它們像其他存儲過程那樣被調用,和其他存儲過程不同的是,它們并不是由簡單的 SQL 語句組成。大多數虛拟系統存儲過程都是私用的,并且沒有寫入文檔。對于遊标過程,所有 API 都顯露其自有的一組遊标 API 模型和它們自己的遊标操作函數,是以沒有必要為存儲過程本身編寫文檔。即使是在 Transact-SQL 語言中,也有顯露遊标的文法,可以使用 DECLARE、OPEN、FETCH 等,是以完全沒有必要為虛拟系統存儲過程編寫文檔,例如 sp_cursor,因為這些過程隻在内部使用。

ODBC 和 OLE DB 中出現了帶參數的查詢和準備/執行模型的概念。在 SQL Server 7.0 以前的版本中,這些概念是由客戶機 API 中的代碼實作的。在 SQL Server 7.0 中,Microsoft 為這些概念添加了對“關系伺服器”的支援,并且通過新的虛拟系統存儲過程顯露了這種支援。本文後面還要介紹這些功能,以及伺服器如何支援這些功能。通過 sp_executesql 過程對帶參數的查詢的支援,被認為對直接 Transact-SQL 和 DB-Library 的使用特别有用,是以将其寫入了文檔。準備/執行的過程,被 ODBC 驅動程式和 OLE DB 提供程式專用。

這樣,可以與 SQL Server 通信的所有客戶機程式,都建立在這三組功能之上:TDS 協定、目錄存儲過程和虛拟系統存儲過程。

伺服器結構

SQL Server,或更确切一點地說,是“SQL Server 關系伺服器”,經常被說成是由兩個主要部分組成,即關系引擎和存儲引擎。正如前面提到過的那樣,已經有很多文獻介紹存儲引擎的細節了,是以本文主要介紹關系引擎的功能。圖 3 給出了 SQL Server 關系引擎部分的主要元件。所給出的元件可以分為三組子系統。左邊的元件編譯查詢,包括查詢優化器。查詢優化器是所有關系資料庫引擎中的最神秘的部分之一,從性能的角度看也是最重要的部分。查詢優化器負責提取 SQL 語句中的非過程請求部分,并将其翻譯成一組磁盤 I/O、過濾以及其他能夠高效地滿足該請求的過程邏輯。圖中右側是執行基礎結構。這裡實際上隻有很少的功能。當編譯元件的工作完成之後,所産生的結果隻需用很少幾個服務即可直接執行。

 

Microsoft SQL Server 查詢處理器的内部機制與結構

圖 3. 伺服器結構

圖的中間是稱為 SQL Manager 的部分。SQL Manager 控制着 SQL Server 内部的所有資料的流動。SQL Manager 控制着 RPC 消息,在 SQL Server 7.0 中,絕大多數來自客戶機的功能調用都是通過 RPC 消息進行的。上一節中介紹的虛拟系統存儲過程邏輯上也是 SQL Manager 的一部分。通常,作為 TDS SQL 語言消息的 SQL 語句直接在編譯一端執行,與早期版本相比,SQL Server 7.0 較少使用這種方法,但還算是比較常見的。執行結果由稱為 ODS 的執行引擎中的元件格式化為 TDS 執行結果消息。

絕大多數輸出都來自圖中的執行端,而且輸出結果也真正出自表達式服務。“表達式服務”庫是進行資料轉換、謂詞評估(過濾)以及算法計算的元件。它還利用了 ODS 層,把輸出結果格式化為 TDS 消息。

還有幾個元件,我們隻是在這裡簡單地提一下,這些元件在關系引擎内部提供附加服務。這些元件中的一個是目錄服務元件,用于資料定義語句,例如 CREATE TABLE、CREATE VIEW 等。目錄服務元件主要放在關系引擎中,但是實際上大約有三分之一的目錄服務元件是在存儲引擎中運作的,是以可以看作是共享元件。

關系引擎中的另一種元件是“使用者模式排程程式 (UMS)”,這是 SQL Server 自己内部的纖程和線程規劃器。把任務配置設定給纖程或線程是一種非常複雜的内部機制,取決于對伺服器如何配置,以及在 SMP 系統中允許 SQL Server 進行處理器之間的适當的負載平衡。UMS 還可以避免 SQL Server 由于同時運作太多的線程而導緻性能過低。最後,還有大家熟悉的系統過程,邏輯上它們也屬于關系引擎的一部分。這些元件肯定不是伺服器代碼,因為可以很容易地使用 sp_helptext 檢查定義這些過程的 Transact-SQL 代碼。但是,系統過程被作為伺服器的一部分來對待,因為系統過程的用途是顯露重要的伺服器能力,像系統表一樣,以供應用程式在更高的層次上和更适當的層次上使用。如果應用程式開發人員将較高層次的系統過程 — 更容易使用 — 作為一種接口,即使随着版本的更新,原始層次上的系統表發生變化時,應用程式仍然可以繼續使用。

處理 SQL 語句時的客戶機/伺服器互動

下面我們将讨論當客戶機應用程式與 SQL Server 互動時客戶機的動作。以下是一個 ODBC 調用的例子:

SQLExecDirect(hstmt, "SELECT * FROM parts where partid = 7", SQL_NTS)

(OLE-DB 也有一個與這個調用幾乎直接等價的調用,此處不再讨論這個調用,因為這個調用實際上與 ODBC 調用相同。)該 ODBC 調用取一個 SQL 語句,然後将其發送給 SQL Server 來執行。

在這個具體的查詢語句中,我們從零件表中提取具有特定零件辨別号的所有行。這是特定 SQL 的一個典型例子。在 SQL Server 7.0 以前的版本中,特定的 SQL 與存儲過程的一個顯著差别是,查詢優化器所生成的計劃從不緩存。查詢語句要被讀入、編譯、執行,然後再抛棄計劃。在 SQL Server 7.0 中,正如稍後還要讨論的,實際上提供了可以緩存特定查詢語句的計劃的機制。

在這條語句被送往 SQL Server 之前,還必須要問幾個問題。所有客戶機程式都要提供某種遊标說明,是以客戶機程式在内部必須詢問的一個問題是,程式員請求的是什麼樣的結果集或什麼樣的遊标。最快的類型是在文檔中被稱為預設結果集的遊标。這種遊标由于曆史上的原因被稱為消防站遊标,有時甚至根本不把它作為遊标看待。當 SQL 請求被送到伺服器之後,伺服器開始把結果傳回給客戶機,這個傳回結果的過程持續進行,直到把全部資料集發送完畢為止。這就像一個将資料抽給客戶機的大型消防站。

一旦客戶機程式确定了這是預設結果集,則下一步就是确定是否有參數标記。使用這個 ODBC SQLExecDirect(以及 OLE-DB 中等價的調用)調用的選項之一是,不是在 WHERE 從句中給出像 7 這樣的具體值,而是可以用一個問号來傳遞參數标記,如下所示:

SQLExecDirect(hstmt, "SELECT * FROM parts where partid =?", SQL_NTS)

請注意,您必須分别提供實際的參數值。

客戶機需要知道 SQL 語句中是否有參數标記,或者它是否為真正特定的非參數化 SQL。這将影響到客戶機将用這個語句在内部做什麼,并确定将什麼作為消息真正發送給 SQL Server。在沒有問号時,很明顯,客戶機隻想将這個請求作為 SQL Language TDS 消息發送,然後客戶機将位于流水的末端,并将結果傳回。然後客戶機能将結果傳回給基于應用程式參數的應用程式。客戶機的内部處理選擇會模糊一點,這取決于您通過 ODBC 或 OLE DB API 請求什麼。例如,應用程式不直接請求預設結果集。相反,在 ODBC 中,如果請求一個隻讀的、隻向前的且每次隻給出一行的遊标,那麼對于客戶機内部運作來說,這就是在定義流水遊标(預設結果集)。

流水遊标有一個主要問題。除非客戶機已将所有的行全部接收完畢,客戶機不能将任何其他 SQL 語句向下發送給伺服器。因為結果集可能有很多行,是以有些應用程式使用流水遊标時不能順利運作。後面将要描述的隻向前的快速遊标,是 SQL Server 7.0 版的一個新特點,尤其适合于處理這種情況。

在 SQL Server 7.0 版之前,SQLExecDirect 調用在很大程度上是以相同方式處理的,而不管是否用參數标記來代替常數。如果您定義一個參數标記,客戶機将實際取您通過不同調用提供的值(本節的開始示例中的值“7”),并将它插入問号處。然後,使用代替值的新語句被向下發送,作為一個特定的 SQL 語句。在伺服器上使用參數化的 SQL 沒有任何好處。

然而,在 SQL Server 7.0 版中,如果 SQLExecDirect 使用了參數标記,向下發送給 SQL Server 的 TDS 消息便不是 SQL 語言消息。相反,它被下發給使用 sp_executesql 過程的伺服器,是以,就 TDS 協定來說,它是 RPC。在客戶機上,結果基本上相同。客戶機将取回資料流水。

如果您不想取回這個資料流水,則可以始終使用塊遊标或可滾動遊标。在這種情況下,資料流變得大不相同。調用是對通過 SQL 文本中的 sp_cursoropen 輸入點(這些虛拟存儲過程之一)進行的。該 sp_cursoropen 利用 SQL 來增加附加邏輯,以使其滾動,它潛在地将某些結果重定向到一個臨時表,然後用句柄給遊标一個響應,表明遊标現在是打開的。仍然在程式員的控制之外,客戶機調用 sp_cursorfetch,将一行或多行轉到客戶機上,然後傳回到使用者應用程式。客戶機還可使用 sp_cursor 來重新配置遊标,或改變某些統計數字。在您處理完遊标之後,客戶機将調用 sp_cursorclose。

讓我們看一個簡單的情況,即隻傳回一行給客戶機。至于預設的結果集,需要從客戶機到伺服器往返發送一次消息。SQL 消息(或 sp_executesql)向下發往伺服器,然後結果傳回來。在同一行(非流水)的遊标情況下,您會看到傳統情況下能用 SQL Server 看見的東西。一個往返行程用于打開,一個往返行程用于取得資料,一個往返行程用于關閉。這個過程使用消息的次數是預設結果集使用的三倍。在 SQL Server 7.0 中,有一種所謂隻向前的快速遊标, 它使用同樣的遊标結構。它與流水的表現不一樣,因為在發送任何附加 SQL 消息之前,它不需要您處理全部結果行。是以,如果您帶回 5 行,還有更多的資料,您仍能将更新向下發送給伺服器。

一個隻向前的快速遊标在伺服器上比正常遊标更快,它讓您指定兩個附加選項。一個稱為自動取數,另一個稱為自動關閉。自動取數将傳回第一個行集合,作為打開的響應消息的一部分。自動關閉在讀完最後一行後自動關閉遊标。因為它是隻向前的和隻讀的,是以不能復原。SQL Server 隻傳回一個帶有說明遊标已關閉的最後資料集的消息。如果您正在使用隻向前的快速遊标,則在行數少的消息裡,您可向下與同一往返行程通信。如果您有很多行,則您至少還要對每一行塊支付附加開銷。如果您使用隻向前的快速遊标,那麼遊标處理會更加接近預設的結果集。

SQLExecDirect 模型流程如圖 4 所示。

Microsoft SQL Server 查詢處理器的内部機制與結構

圖 4. 客戶機/伺服器互動

準備/執行模型

除了執行直接模型(在 ODBC 中用 SQLExecDirect 調用)外,在 ODBC 和 OLE-DB 中,還有一種執行模型,稱為準備/執行模型。定義要執行的 SQL,是作為一個獨立于實際執行 SQL 的步驟來完成的。以下是 ODBC 中的一個例子:

SQLPrepare(hstmt, "SELECT * FROM parts where partid = ?", SQL_NTS)

SQLExecute(hstmt)

在 SQL Server 7.0 版本之前,準備/執行從來都不是 SQL Server 的本機模式。如今在 7.0 版本中,有兩個提供本機接口的虛拟系統存儲過程。對于準備調用,我們要再次研究遊标的類型,然後調用 sp_prepare 或 sp_cursorprepare。這些過程會完成 SQL 或存儲過程的編譯,但不會實際執行計劃。相反,虛拟系統存儲過程隻是傳回該計劃的句柄。現在,應用程式可以反複地執行 SQL 了,例如傳入不同的參數值,而不需要重新編譯。

在 SQL Server 6.5 中,由于沒有本機接口,需要模拟準備和執行兩個階段。可以通過下面的兩種方法做到這一點。在第一種方法中,不會真正出現準備階段。隻有執行部分傳回中繼資料(有一些選項可以做到這一點),是以 SQL Server 可以把結果的格式描述傳回給應用程式。在第二種方法中,SQL Server 實際上建立一個特定存儲過程,這個過程是單個使用者私用的,不能共享計劃。這第二種方法可能會占滿 tempdb 資料庫的空間,是以大多數應用程式開發人員都通過 ODBC 配置對話框中的複選框,關閉此選項,以使用第二種方法。

在 SQL Server 7.0 中,準備/執行方法是 SQL Server 的本機功能。準備好 SQL 語句之後,才會執行它。至于預設的結果集,應用程式隻需要調用 sp_execute,提供準備操作生成的句柄,語句就會被執行。對于遊标,與其他遊标處理過程看起來很相似,事實上,它也具有相同的特性,包括如果遊标是快速隻前向型,還可以使用 autofetch 和 toclose。

準備/執行操作的流程如圖 5 所示。

 

Microsoft SQL Server 查詢處理器的内部機制與結構

圖 5. 準備/執行模型

調用存儲過程

存儲過程一般是從 ODBC 和 OLE-DB,通過發送 SQL 語句給使用 ODBC 标準 CALL 文法調用過程的 SQL Server 來調用。其應類似于以下語句:

SQLExecDirect(hstm, "{call addorder(?)}", SQL_NTS)

對于預設結果集,這是一個簡單的流,因為這正是 RPC 消息原本要處理的對象。客戶機向伺服器發送 RPC 消息,并擷取來自存儲過程的處理結果。如果是遊标,則情況稍微複雜一些,客戶機需要調用 Sp_cursoropen,就像其他遊标一樣。Sp_cursoropen 含有内部邏輯,檢測該存儲過程是否隻包含一條 SELECT 語句。如果是,則對該 SELECT 語句打開一個遊标。如果該存儲過程中不是一條 SELECT 語句,則客戶機會得到一個訓示,說明"我們為您打開結果集,但是我們将以流水的方式傳回資料流,您可以把這個資料流提供給使用者"。

存儲過程執行流程如圖 6 所示。

 

Microsoft SQL Server 查詢處理器的内部機制與結構

圖 6. 調用存儲過程

SQL Manager

前面已經提到過的 SQL Manager 驅動很多伺服器處理過程,它實際上是伺服器的心髒。SQL Manager 處理所有調用存儲過程的請求,管理過程緩存,擁有虛拟系統存儲過程,在稍後要介紹的特定查詢的自動參數化過程中也要涉及。如果您有與本文類似的描述 SQL 6.5 或更老版本的文章,則不會讀到有關 SQL 管理器的讨論,然而,您會讀到一些完成 SQL 管理器工作的一些不同的元件。但是在 SQL Server 7.0 中,這些元件被統一為 SQL 管理器,通過系統驅動查詢語句的處理。

在一般情況下,當要求 SQL 管理器為您做某些工作時,通過 RPC 消息調用 SQL 管理器。但是,當通過 SQL 消息發送 SQL 語句并進入引擎編譯時,也會用到 SQL 管理器。當存儲過程或批處理程式包含 EXEC 語句時,也會調用 SQL 管理器,因為 EXEC 實際上就是調用 SQL 管理器。如果該 SQL 語句傳送了下面就要讨論的一個自動參數化模闆,則需要調用 SQL 管理器對該查詢進行參數化處理。當特定查詢語句需要裝入緩存時,也要調用 SQL 管理器。

編譯與執行

現在讨論在 SQL Server 中編譯和執行的一般流程。需要注意的是編譯和執行在 SQL Server 内部是兩個不同的階段。SQL Server 編譯查詢語句和執行該語句之間的間隔時間可能非常短,隻有幾個毫秒,也可能是幾秒鐘、幾分鐘、幾小時甚至幾天。在編譯過程中(這個過程包括優化),我們必須區分什麼樣的知識可以用于編譯。并不是所有對編譯有用的知識對執行也起作用。您必須把編譯和執行了解為兩個不同的活動,即使您發送并立即執行的是特定 SQL 查詢語句。

當 SQL Server 可以開始處理查詢語句時,SQL Manager 要在緩存内進行查找,如果沒有找到該語句,則必須編譯該語句。編譯處理要完成以下幾件工作。首先,要進行分析和正常化。分析就是剖析該 SQL 語句,将其轉換成更适合計算機處理的資料結構。分析還要驗證文法的正确性。分析不進行表名和列名合法性等檢查,這些工作在正常化階段完成。正常化主要是解析 SQL 語句中引用的對象,轉換成實際的資料庫對象,檢查請求的語義是否有意義。例如,試圖執行一個表,這在語義上就是錯誤的。

下一步是編譯 Transact-SQL 代碼。Transact-SQL 和 SQL 本身都讓人有點兒困惑,Microsoft 的開發人員也像别人一樣經常互換兩個詞。但是,這兩者之間還是有重要差别的。SQL 包括所有 DML 語句:INSERT、UPDATE、DELETE 和 SELECT。SQL Server 還有一種包括這些 DML 語句的語言,稱為 Transact-SQL,也就是 TSQL。TSQL 提供過程結構:IF 語句、WHILE 語句、局部變量聲明等。伺服器内部對 SQL 和 TSQL 的處理方法完全不同。TSQL 的過程邏輯要由知道如何進行過程化處理的引擎來編譯。

SQL 語句本身由典型的查詢優化器來處理。優化器必須把基于集合的 SQL 語句的非過程化的請求,翻譯成可以被高效執行并傳回所需結果的過程。除非特别說明,我們在以下讨論編譯時,均指 TSQL 的編譯和 SQL 語句的優化。

上面已經提到,編譯和執行是兩個不同的查詢處理階段,是以,優化器完成的工作之一是基于相當穩定的狀态進行優化。您可以注意到,SQL Server 可能會根據語句所滿足的條件重新編譯,是以狀态并不是永遠穩定的,但也不是處于不停的變化之中。如果優化器使用的資訊變化太劇烈、太經常 - 并發處理器的數量和鎖的數量不穩定 - 則必須不斷重新進行編譯,而一般來說編譯是比較耗時的。例如,SQL 語句的運作時間為百分之一秒,而編譯可能需要占用半秒。最理想的情況是,SQL Server 能夠隻編譯語句一次,而執行成千上萬次,不必每次執行該語句時都重新編譯它。

編譯階段的最終産品是查詢計劃,放在過程緩存中。便宜的特定 SQL 計劃并不放在緩存中,不過這隻是個小問題。我們不希望緩存被不太可能重複執行的内容占滿,一般來說,特定 SQL 語句的計劃是最不可能反複使用的了。如果語句編譯已經很便宜(小于百分之一秒),則沒有必要再把計劃放入緩存,用不太可能重新使用的計劃占用緩存。

把計劃放入緩存之後,SQL Manager 按照執行要求邏輯進行檢查,确定是否有更改的内容,是否需要重新編譯。即使編譯到執行之間時間間隔隻有幾毫秒,也可能有人會執行一條資料定義語句 (DDL),為關鍵的表加了索引。這種可能性不大,但是确實存在,是以 SQL Server 必須考慮這一點。有幾種情況 SQL Server 必須重新編譯存儲規劃。中繼資料的修改,例如增加或删除索引,是重新編譯的最主要的原因。伺服器必須确信所使用的計劃反映了索引的目前狀态。

重新編譯的另一種原因是統計情況發生變化。SQL Server 還維護不少資料使用頻率的統計資訊。如果資料使用頻率分布情況變化很大,則可能需要另一個查詢計劃以便更有效地執行。SQL Server 跟蹤表資料插入和删除的統計資料,如果資料修改的數量超過根據表的容量變化的某一門檻值,則需要根據新的分布資料重新編譯計劃。

圖 7 給出了編譯和執行過程的流程。

Microsoft SQL Server 查詢處理器的内部機制與結構

圖 7. 編譯與執行

注意,實際參數的改變并不會導緻重新編譯,環境的改變,例如可用記憶體的增加或所需資料的增加,也不會導緻重新編譯。

執行是比較簡單的,如果需要執行的查詢很簡單,如"插入一行",或從帶有唯一索引的表中查詢資料,則執行處理會非常簡單。但是,很多查詢都要求大量的記憶體以提高運作效率,或至少從所增加的記憶體得到好處。在 SQL Server 6.5 中,每個查詢能夠使用的記憶體限制在 0.5MB 或 1MB 以下。有一個控制查詢記憶體使用的參數,稱為排序頁。顧名思義,它主要是限制可能占用大量記憶體的排序操作。不管要處理的排序有多大,在 SQL Server 6.5 中,記憶體的使用不能超過 1MB。即使您使用的機器上配置了 2GB 記憶體,需要對數百萬行資料排序,也不能突破限制。顯然,複雜的查詢不能高效執行,是以 SQL Server 開發人員增加了 SQL Server 7.0 的能力,使得單個查詢可以使用大量的記憶體。

另一個問題随之而來。一旦您開始允許查詢使用大量記憶體,就必須确定如何把記憶體配置設定給可能需要記憶體的很多查詢。SQL Server 按照以下方法解決這個問題。當查詢計劃優化之後,優化器要确定有關給該查詢使用的記憶體的兩部分資訊。第一,該查詢有效執行所需要的最小記憶體,該參數與查詢計劃一起存放。優化器還要确定該查詢可以獲益的最大的記憶體量。例如,如果要排序的整個表隻有 100MB,配置設定 2GB 記憶體就沒什麼幫助了。您需要的隻是 100MB,這個最大有用記憶體參數随查詢計劃一起存放。

當 SQL Server 開始執行計劃時,該計劃被傳遞給一個所謂記憶體授權排程程式的例程中。這個授權排程程式要完成幾項有趣的工作。首先,如果授權排程程式要處理的查詢在計劃中沒有排序或雜湊操作,則 SQL Server 知道該查詢不會需要很多記憶體。在這種情況下,不需要記憶體授權排程程式進行判斷。該計劃會立即執行,是以典型的事務處理請求會完全旁路這種判斷機制。記憶體授權排程程式還設有多個隊列處理不同容量的請求。記憶體排程程式優先處理較小的請求。例如,如果有一個查詢要求"提取前 10 個",并且隻需要對 20 行排序,則雖然需要經過記憶體授權排程程式,但是要釋放該查詢并且很快排程。伺服器需要并行或并發執行許多這種查詢。

如果有很大的查詢,您希望一次隻運作幾個查詢,讓它們占有所需的更多記憶體。SQL Server 确定一個由 4 X(系統中的 CPU 個數)得到的數。如果可能,SQL Server 會同時運作那個數量的查詢,為它們配置設定高效運作所需要的最小記憶體。如果還剩有記憶體,則一部分查詢會允許占用最大高效記憶體。SQL Server 試圖既為查詢配置設定盡可能多的記憶體,又讓盡可能多的查詢同時運作在系統中。

能夠使用最大高效記憶體對某些操作很重要,例如夜間運作的批處理過程。您可能會生成很大的報表,或重建立立索引。這些查詢可能使用大量記憶體,這種機制可以動态調整對記憶體的需求。是以,如果如果在隊列中等待處理的查詢不多,則記憶體授權排程程式會經常配置設定給查詢最大需要的記憶體。如果白天的機器負載很重,則就不能同時運作太多的查詢。這些查詢會得到有效運作所需最小的記憶體,讓記憶體為更多的查詢共享。

一旦排程程式說現在可以為請求配置設定記憶體,則計劃即被"打開",開始實際運作。計劃會一直運作直到完成。如果查詢使用了預設結果集模型,則計劃會一直運作到檢索到所有結果為止,然後把結果傳回給客戶機。如果使用的是遊标模型,則處理過程略有不同。每個客戶機請求隻提取一塊資料,并不是所有資料。當每個結果塊傳回給客戶機之後,SQL Server 必須等待客戶機的下一個請求。在等待時,整個計劃就會睡眠。這意味着要釋放一些鎖,要釋放一些資源,并保留一些斷點資訊。這些斷點資訊使得 SQL Server 能夠傳回到睡眠之前的狀态,使得執行可以繼續。

過程緩存

我們在前面已經多次提到 SQL Server 的過程緩存。需要注意的是,SQL Server 7.0 的過程緩存與以前的版本有很大不同。在早期的版本中,有兩個有效配置值用于控制過程緩存的容量:一個是定義 SQL Server 總可用記憶體的固定容量,另一個是供存儲查詢計劃使用的記憶體百分比(扣除滿足固定需要的記憶體)。在老版本中,特定 SQL 語句從不存入緩存,隻有存儲過程計劃才存入其中。在 SQL Server 7.0 中,記憶體的總容量是動态的,用于查詢計劃的空間也是經常變化的。

在處理查詢時,SQL Server 7.0 首先會問的是:這個查詢既是特定的又是易于編譯的嗎?如果是,SQL Server 就根本不會将其寫入緩存中。将來重新編譯這些計劃比把複雜的計劃或資料頁推出記憶體更合算。如果查詢不是特定的或不易于編譯,則 SQL Server 會從緩存區中配置設定一些緩存記憶體存儲該計劃,因為該緩存區是 SQL Server 7.0 用來滿足 99% 記憶體需求的唯一來源。在少數特殊情況下,SQL Server 會直接從作業系統中配置設定大塊記憶體,但是這種情況極為罕見。SQL Server 的管理是集中式的。

寫入緩存的除計劃外,還有反映通過編譯該查詢實際建立該計劃的成本的成本因子。如果這是一個特定計劃,則 SQL Server 将它的成本設定為 0,表示可以立即将它撤出過程緩存。對于特定 SQL,雖然有可能被重複使用,但可能性很小,如果系統記憶體緊張,總是願意首先撤出特定語句的計劃。這樣,特定查詢的計劃是最适合清出緩存的對象。如果查詢不是特定的,則 SQL Server 會把該成本設定為實際編譯查詢的成本。這些成本是以磁盤 I/O 為機關的。如果從磁盤中讀出一個資料頁,則有一個 I/O 成本。在編譯計劃時,資訊從磁盤中讀出,包括統計資料和查詢本身的文本。SQL 要進行附加的處理,而且這處理工作被正常化為 I/O 成本。現在,建立過程的成本可用執行 I/O 的成本表示。該成本非常恰當反映了,與打算用磁盤緩存的資料量相比,管理實際打算配置設定給存儲過程和任何種類查詢計劃的緩存量的能力。該成本被計算出來之後,該計劃就會被寫入緩存。

如果另一個查詢可以重新使用該計劃,則 SQL Server 要再次判定計劃的類型。如果是一個特定計劃,SQL Server 會把成本加 1。這樣,如果特定計劃确實要被重新使用,則它會在緩存中稍作停留,停留時間越長,成本就增加越多。如果該計劃經常被重新使用,則成本會一次增加一個機關地不斷增長,直到增長到其實際編譯成本。該成本和設定的成本一樣高。不過該計劃經常被重複使用;如果同一使用者或其他使用者不斷重新送出完全一樣的 SQL 文本,該計劃就會留在緩存中。

如果查詢不是特定的,也就是說是一個存儲過程、帶參數的查詢或自動參數化的查詢,則每次該計劃被重新使用時,成本都會設定回原來的值。隻要計劃被重新使用,就會留在緩存中。即使有一段時間沒有被使用,取決于最初的編譯代價的高低,計劃停留在緩存中的時間也有長短。

遲緩寫入器(Lazywriter) 是使計劃過時的機制,負責在必要的時候從緩存中删除計劃。遲緩寫入器實際上是存儲引擎的一部分,但是因為遲緩寫入器對于查詢處理機制是如此重要,我們還是在這裡進行讨論。遲緩寫入器管理查詢計劃記憶體使用的機制與管理頁面的機制一樣,因為 SQL Server 7.0 計劃存儲在普通緩沖存儲器中。遲緩寫入器要檢查系統中所有的緩沖器标題。如果系統的記憶體不緊張,檢查的次數就很少;如果開始緊張,則遲緩寫入器就會經常運作。當遲緩寫入器運作時,它要檢查緩沖區标題,并檢查緩存區中該頁面的目前成本。如果成本為 0,則意味着自從上次遲緩寫入器檢查以來,該頁面沒有被使用過,于是遲緩寫入器就會釋放該頁面,以便為系統增加可用記憶體,用于頁面 I/O 或其他計劃。此外,如果該緩沖區包含過程計劃,則遲緩寫入器會調用 SQL 管理器,以完成一些清理工作。最後,該緩沖區會被放到可用記憶體表中供重新使用。

如果與緩沖區關聯的成本大于 0,則遲緩寫入器會把成本減 1,并繼續檢查其他緩沖區。這成本實際上反映的,某計劃若是沒被使用,它在緩存中還能存在多少個遲緩寫入器的檢查周期。這種算法,除了如果對象是存儲過程則調用 SQL Manager 這一步之外,對緩存中的計劃和緩存的資料或索引沒有什麼差別。遲緩寫入器并不知道對象是否存儲過程,這種算法很好地平衡了磁盤 I/O 對緩存的使用和存儲過程計劃對緩存的使用。

您會發現,如果計劃的編譯成本很高,那麼即使很長一段時間都沒有被重新使用,也仍然會停留在緩存中,這是因為其初始成本太高了。經常被重新使用的計劃也會長期停留在緩存中,這是因為每當它被重新使用時其成本已被重新設定,遲緩寫入器不會看到它的成本降為 0。

處理客戶機的 SQL

下面再看看送出 SQL 語句之後的處理過程。首先,我們将研究客戶機向 SQL Server 發送 RPC 事件。因為 SQL Server 收到了 RPC 事件,是以它會知道該事件是某種參數化的 SQL;它是準備/執行模型,或者是 EXECUTESQL。SQL Server 需要建構一個緩存鍵,以辨別這個具體的 SQL Server 文本。如果 SQL Server 處理的是實際的存儲過程,則不需要建立它自己的鍵;直接使用該過程的名稱即可。對于通過 RPC 調用發來的簡單 SQL 文本,則通過雜湊該 SQL 文本來建立緩存鍵。此外,該鍵還要反映一定的狀态資訊,如某些 ANSI 設定。使所有 ANSI 設定為 ON 的連接配接和另一個使所有 ANSI 設定為 OFF 的連接配接,即使它們來自相同的查詢,也不能使用相同的計劃。處理過程是不同的。例如,如果一個連接配接把 concat_null_yields_null 設定為 ON,另一個把 concat_null_yields_null 設定為 OFF 的連接配接,即使它們執行的是完全相同的 SQL 文本,但所産生的結果則完全不同。這樣,SQL Server 可能需要在緩存中儲存計劃的多個版本,每個版本對應于一個不同的 ANSI 設定組合。啟用的選項設定是鍵的一部分,而鍵字是使用這種緩存處理機制檢查對象的核心,是以 SQL Server 建立這種鍵并用來檢查緩存。如果在緩存中沒有發現該計劃,則 SQL Server 會按照前面介紹的方式編譯該計劃,并把該計劃與鍵一起存入緩存中。

SQL Server 還需要确定該指令是否是準備操作,這意味着該計劃應該隻編譯但不執行。如果是準備操作,則 SQL Server 會給客戶機傳回一個句柄,供客戶機在以後檢索并執行該計劃。如果不是一個準備操作,則 SQL Server 提取并執行該計劃,就像最初從緩存中找到該計劃一樣。

準備/執行模型為緩存管理增加了複雜因素。預備給出了今後能夠執行該計劃的句柄。應用程式可以在幾小時或幾天之内保持該句柄是激活的,以定期執行計劃。即使需要在緩存中為更多的活動計劃或資料頁面騰出空間,也不能使該句柄無效。SQL Server 實際所做的就是将計劃放入緩存,此外還從預備操作中将 SQL 儲存到更加緊湊的空間。如果空間緊張,則可按前述的方式釋放計劃所占用的空間,但仍有 SQL 的副本準備着。如果客戶機要執行預備的 SQL,但在緩存中沒有找到計劃,則 SQL Server 能夠檢索到該文本并編譯它,再将它放回緩存中。這樣,緩存中的 16 千位元組 (KB) 或更多的頁面用來儲存可重用的計劃,而長期占用的空間或許是存儲在其他處的 SQL 代碼的 100 或 200 位元組。

處理來自客戶機的語句時的另一種情況是,查詢是作為 SQL 語言事件出現的。除了一點以外,此流程并無太大的差異。在這種情況下,SQL Server 試圖使用稱為自動參數化的技術。SQL 文本與自動參數化模闆相比對。自動參數化是個棘手的問題,是以,過去一直能夠利用共享的 SQL 的其他資料庫管理産品, 一般并沒有提供這一選項。随之而來的問題是,如果 SQL Server 自動地參數化每個查詢,那麼對于随後送出的某些特定值而言,這些查詢中的某些(或絕大多數)将獲得非常糟糕的計劃。在程式員将參數标記放在代碼之中的場合下,其假定是程式員知道所期望的值的範圍,并願意接受 SQL Server 提供的計劃。但當程式員實際補充一個特定的值,并且 SQL Server 決定将該值當做一個可變的參數來對待時,所産生的任何适合于某個值的計劃可能不适合于後續的值。利用存儲過程,通過在過程中放入 WITH RECOMPILE 選項,程式員可以強制産生新的計劃。利用自動參數化,程式員無法指出必須為每一個新值開發新的計劃。

當 SQL Server 處理自動參數化時,它是非常保守的。被安全地自動參數化的查詢有一個模闆,并且隻有比對模闆的查詢才能應用自動參數化。例如,假設有這樣一個查詢,其中包含帶有等于操作符、但沒有連接配接的 WHERE 子句,WHERE 子句中的列帶有唯一的索引。SQL Server 知道絕對不會傳回一行以上,而且計劃将總是使用那個唯一的索引。SQL Server 絕對不會考慮掃描,實際值絕對不會以任何方式改變計劃。對于自動參數化而言,這種查詢是安全的。

如果查詢比對自動參數化模闆,則 SQL Server 自動用參數标記(例如 @p1、@p2)代替文字,并且這就是我們發送到伺服器的内容,正如它是 sp_executesql 調用一樣。如果 SQL Server 認為該查詢對自動參數化并不安全,則客戶機将向 SQL Server 發送文字的 SQL 文本,以此作為特定的 SQL。

編譯

現在讓我們更詳細地讨論一下編譯和優化。在編譯過程中,SQL Server 分析語句,并建立所謂的次序樹,即語句的内部表述。這是 SQL Server 6.5 實際保留在 SQL Server 7.0 中的幾個資料結構之一。該次序樹是正常化的。正常化程式的主要功能是執行綁定。綁定包括檢驗表和列的存在,以及裝載有關表和列的中繼資料。有關必需的(隐含的)轉換資訊也附加在次序樹上,例如,如果查詢試圖向數字值添加整數 10,則 SQL Server 将向該樹插入隐含的轉換。正常化還用視圖的定義代替對該視圖的引用。最後,正常化執行一些基于文法的優化。如果該語句是傳統的 SQL 語句,則 SQL Server 從關于該查詢的次序樹中提取資訊,并建立稱為查詢圖表的特殊結構,設定查詢圖表是為了使優化器工作非常有效。然後優化該查詢圖表,一個計劃就産生了。

優化

SQL Server 優化器其實是由獨立的段組成的。第一段是一個非基于成本的優化器,稱為瑣細計劃優化。瑣細計劃優化的完整概念是,當 SQL 語句确實隻有一個可變計劃時,基于成本的優化太昂貴了。最好的例子是,帶 VALUES 子句的 INSERT 語句組成的查詢。它隻可能有一個計劃。另一個例子是,所有的列都在唯一的封面索引(且沒有其他列的索引)中的 SELECT 語句。這兩例中,SQL Server 隻要簡單地生成一個計劃,用不着在多個計劃選一個更好的方案。瑣細計劃優化器可找到真正顯而易見的計劃,而且通常非常便宜。是以,最簡單的查詢在處理的前期就趨于被清除,優化器不花很多時間來搜尋一個好計劃。這是好事,因為随着 SQL Server 将雜湊連接配接、合并連接配接和索引相交增加到其處理技術清單上,SQL Server 7.0 版上的潛在計劃數呈天文數字增長。

如果瑣細計劃優化器不能找到一個計劃,SQL Server 便進入優化的下一部分,稱為簡化。簡化是查詢本身的文法變換,尋找可交換的特性和可重新排列的運算。SQL Server 可進行常數合并,以及無需考慮成本或分析索引是什麼但能得出更有效查詢的其他運算。SQL Server 然後上載關于索引和列的統計資訊,并輸入優化的最後的主要部分,即基于成本的優化器。

基于成本的優化有三個階段。第一個基于成本的階段,稱為交易處理階段,查找簡單請求的計劃,即典型的交易處理系統。這些請求一般比由瑣細計劃優化器處理的那些請求要複雜些,并要求比較衆多計劃查找出成本最低的計劃。當交易處理階段完成時,SQL Server 便将找到的成本最低的計劃與内部門檻值進行比較。門檻值用于決定是否要求進一步的優化。如果計劃成本比門檻值低,那麼,進行附加優化比隻執行已找到的計劃成本要高。是以,SQL Server 不做進一步優化,并使用交易處理階段找到的計劃。

如果交易處理階段找到的計劃,仍比該階段的門檻值貴,SQL Server 便進入第二個階段。這個階段有時稱為 QuickPlan 階段。QuickPlan 階段擴大搜尋範圍來尋找一個好計劃,包括選擇好的、适度複雜的查詢。QuickPlan 檢查可能的計劃範圍,完成之後,将最佳計劃的成本與第二個門檻值進行比較。因為在交易處理階段,如果發現了一個成本比門檻值低的計劃,優化便終止,并使用那個計劃。一般來說,SQL Server 6.5 版中已有的查詢的計劃,在 SQL Server 7.0 版中也應當是最佳的,這個計劃将要麼被瑣細計劃優化器找到,要麼被基于成本的優化的頭兩個階段中的一個發現。這些規則被有意地組織起來以達到這個目的。這個計劃将很可能由使用單一的索引和使用嵌套循環聯合組成。

優化的最後階段,稱為完全優化,旨在對複雜和非常複雜的查詢産生一個好計劃。對複雜的查詢來說,QuickPlan 産生的計劃,經常被認為比繼續搜尋一個更好的計劃要昂貴得多,而完全優化将被執行。在完全優化中,實際上有兩個适用的獨立選擇。如果 QuickPlan 階段産生的最佳成本比"并行成本門檻值"的配置值要高,并且如果伺服器是一個多處理器機器,那麼優化器的最後階段将涉及查找一個能在多個處理器上并行運作的計劃。如果 QuickPlan 階段的最佳計劃的成本比配置的"并行成本門檻值"低,那麼,優化器将隻考慮串行計劃。完全優化階段能執行各種可能性,而且很耗時,因為在這最後階段必須找到一個計劃。優化器仍可能沒有檢查每個可得到的計劃,因為它将任何潛在的計劃成本與優化中得出此結果的成本進行比較,并且它估算繼續試用不同優化的可能成本。在某些情況下,優化器可能認為,使用現有的計劃比繼續查找更優方案還要便宜,而且支付繼續優化的附加編譯成本将不具備高的成本效率比。在這最後階段處理的各種查詢的計劃一般隻使用一次,是以,幾乎沒有這樣的機會:為編譯和優化所付出的額外代價,會在後續執行的計劃重用中一次結清。那些後續執行很可能不會發生。

找到一個計劃後,該計劃便變為優化器的輸出,然後 SQL Server 在執行該計劃之前,周遊前面已讨論過的全部緩存機制。您應該意識到,如果完全優化階段産生了該查詢的并行計劃,并不一定意味着該計劃将在多個處理器上執行。如果機器很忙,而且不支援在多個 CPU 上運作單一的查詢,該計劃則使用單一的處理器。

執行

查詢處理的最後一步是執行。除了這一小段外,我們不會再讨論執行的詳細過程。執行引擎采用優化器生成的計劃,并執行之。處理實際執行以外,執行引擎還為要運作的處理器排程線程,并提供線程間的通信。