圖 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 所示。
圖 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 給出了編譯和執行過程的流程。
圖 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 的成本表示。該成本非常恰當反映了,與打算用磁盤緩存的數據量相比,管理實際打算分配給存儲過程和任何種類查詢計劃的緩存量的能力。該成本被計算出來之後,該計劃就會被寫入緩存。
圖 8 顯示計算計劃成本並將其寫入緩存的流程。
圖 8. 將計劃寫入緩存
如果另一個查詢可以重新使用該計劃,則 SQL Server 要再次判定計劃的類型。如果是一個特定計劃,SQL Server 會把成本加 1。這樣,如果特定計劃確實要被重新使用,則它會在緩存中稍作停留,停留時間越長,成本就增加越多。如果該計劃經常被重新使用,則成本會一次增加一個單位地不斷增長,直到增長到其實際編譯成本。該成本和設置的成本一樣高。不過該計劃經常被重復使用;如果同一用戶或其他用戶不斷重新提交完全一樣的 SQL 文本,該計劃就會留在緩存中。
如果查詢不是特定的,也就是說是一個存儲過程、帶參數的查詢或自動參數化的查詢,則每次該計劃被重新使用時,成本都會設置回原來的值。只要計劃被重新使用,就會留在緩存中。即使有一段時間沒有被使用,取決於最初的編譯代價的高低,計劃停留在緩存中的時間也有長短。
圖 9 顯示從緩存中檢索計劃並調整成本的流程。
圖 9. 從緩存中檢索計劃
遲緩寫入器(Lazywriter) 是使計劃過時的機制,負責在必要的時候從緩存中刪除計劃。遲緩寫入器實際上是存儲引擎的一部分,但是因為遲緩寫入器對於查詢處理機制是如此重要,我們還是在這裡進行討論。遲緩寫入器管理查詢計劃內存使用的機制與管理頁面的機制一樣,因為 SQL Server 7.0 計劃存儲在普通緩沖存儲器中。遲緩寫入器要檢查系統中所有的緩沖器標題。如果系統的內存不緊張,檢查的次數就很少;如果開始緊張,則遲緩寫入器就會經常運行。當遲緩寫入器運行時,它要檢查緩沖區標題,並檢查緩存區中該頁面的當前成本。如果成本為 0,則意味著自從上次遲緩寫入器檢查以來,該頁面沒有被使用過,於是遲緩寫入器就會釋放該頁面,以便為系統增加可用內存,用於頁面 I/O 或其他計劃。此外,如果該緩沖區包含過程計劃,則遲緩寫入器會調用 SQL 管理器,以完成一些清理工作。最後,該緩沖區會被放到可用內存表中供重新使用。
如果與緩沖區關聯的成本大於 0,則遲緩寫入器會把成本減 1,並繼續檢查其他緩沖區。這成本實際上反映的,某計劃若是沒被使用,它在緩存中還能存在多少個遲緩寫入器的檢查周期。這種算法,除了如果對象是存儲過程則調用 SQL Manager 這一步之外,對緩存中的計劃和緩存的數據或索引沒有什麼區別。遲緩寫入器並不知道對象是否存儲過程,這種算法很好地平衡了磁盤 I/O 對緩存的使用和存儲過程計劃對緩存的使用。
您會發現,如果計劃的編譯成本很高,那麼即使很長一段時間都沒有被重新使用,也仍然會停留在緩存中,這是因為其初始成本太高了。經常被重新使用的計劃也會長期停留在緩存中,這是因為每當它被重新使用時其成本已被重新設置,遲緩寫入器不會看到它的成本降為 0。
圖 10 顯示遲緩寫入器處理緩存的流程。
圖 10. 遲緩寫入器處理緩存的流程
處理客戶機的 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。
圖 11 顯示客戶機向 SQL Server 發送請求時的處理流程。
圖 11. 處理客戶機的 SQL
編譯
現在讓我們更詳細地討論一下編譯和優化。在編譯過程中,SQL Server 分析語句,並創建所謂的次序樹,即語句的內部表述。這是 SQL Server 6.5 實際保留在 SQL Server 7.0 中的幾個數據結構之一。該次序樹是正常化的。正常化程序的主要功能是執行綁定。綁定包括檢驗表和列的存在,以及裝載有關表和列的元數據。有關必需的(隱含的)轉換信息也附加在次序樹上,例如,如果查詢試圖向數字值添加整數 10,則 SQL Server 將向該樹插入隱含的轉換。正常化還用視圖的定義代替對該視圖的引用。最後,正常化執行一些基於語法的優化。如果該語句是傳統的 SQL 語句,則 SQL Server 從關於該查詢的次序樹中提取信息,並創建稱為查詢圖表的特殊結構,設置查詢圖表是為了使優化器工作非常有效。然後優化該查詢圖表,一個計劃就產生了。
圖 12 顯示編譯過程流程。
圖 12. 編譯
優化
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 上運行單一的查詢,該計劃則使用單一的處理器。
圖 13 顯示了優化器的處理流程。
圖 13. 優化
執行
查詢處理的最後一步是執行。除了這一小段外,我們不會再討論執行的詳細過程。執行引擎采用優化器生成的計劃,並執行之。處理實際執行以外,執行引擎還為要運行的處理器調度線程,並提供線程間的通信。
摘要
如前所述,SQL Server 的內部機制與結構是一個非常大的主題,遠遠超過了我們能在本文中提供的內容。我們重在直接介紹 SQL Server 與客戶機的交互方式,以及 SQL Server 關系引擎如何處理來自客戶機的請求。我們希望,在了解 SQL Server 如何處理查詢,以及如何和何時編譯或重新編譯它們之後,您就能利用 SQL Server 7.0 的功能和技巧編寫出更好的應用程序。