編譯與執行 現在討論在 SQL Server 中編譯和執行的一般流程。需要注意的是編譯和執行在 SQL Server 內部是兩個不同的階段。 SQL Server 編譯查詢語句和執行該語句之間的間隔時間可能非常短,只有幾個毫秒,也可能是幾秒鐘、幾分鐘、幾小時甚至幾天。在編譯過程中(這個過程包括優化),我們必須區分什么樣的知識可以用于編譯。并不是所有對編譯有用的知識對執行也起作用。您必須把編譯和執行理解為兩個不同的活動,即使您發送并立即執行的是特定 SQL 查詢語句。
當 SQL Server 可以開始處理查詢語句時,SQL Manager 要在緩存內進行查找,如果沒有找到該語句,則必須編譯該語句。編譯處理要完成以下幾件工作。首先,要進行分析和正常化。分析就是剖析該 SQL 語句,將其轉換成更適合計算機處理的數據結構。分析還要驗證語法的正確性。分析不進行表名和列名合法性等檢查,這些工作在正?;A段完成。正?;饕墙馕?SQL 語句中引用的對象,轉換成實際的數據庫對象,檢查請求的語義是否有意義。例如,試圖執行一個表,這在語義上就是錯誤的。
上面已經提到,編譯和執行是兩個不同的查詢處理階段,因此,優化器完成的工作之一是基于相當穩定的狀態進行優化。您可以注意到,SQL Server 可能會根據語句所滿足的條件重新編譯,所以狀態并不是永遠穩定的,但也不是處于不停的變化之中。如果優化器使用的信息變化太劇烈、太經常 — 并發處理器的數量和鎖的數量不穩定 — 則必須不斷重新進行編譯,而一般來說編譯是比較耗時的。例如,SQL 語句的運行時間為百分之一秒,而編譯可能需要占用半秒。最理想的情況是,SQL Server 能夠只編譯語句一次,而執行成千上萬次,不必每次執行該語句時都重新編譯它。
把計劃放入緩存之后,SQL Manager 按照執行要求邏輯進行檢查,確定是否有更改的內容,是否需要重新編譯。即使編譯到執行之間時間間隔只有幾毫秒,也可能有人會執行一條數據定義語句 (DDL),為關鍵的表加了索引。這種可能性不大,但是確實存在,因此 SQL Server 必須考慮這一點。有幾種情況 SQL Server 必須重新編譯存儲規劃。元數據的修改,例如增加或刪除索引,是重新編譯的最主要的原因。服務器必須確信所使用的計劃反映了索引的當前狀態。
重新編譯的另一種原因是統計情況發生變化。SQL Server 還維護不少數據使用頻率的統計信息。如果數據使用頻率分布情況變化很大,則可能需要另一個查詢計劃以便更有效地執行。SQL Server 跟蹤表數據插入和刪除的統計數據,如果數據修改的數量超過根據表的容量變化的某一閾值,則需要根據新的分布數據重新編譯計劃。
執行是比較簡單的,如果需要執行的查詢很簡單,如“插入一行”,或從帶有唯一索引的表中查詢數據,則執行處理會非常簡單。但是,很多查詢都要求大量的內存以提高運行效率,或至少從所增加的內存得到好處。在 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 成本?,F在,建立過程的成本可用執行 I/O 的成本表示。該成本非常恰當反映了,與打算用磁盤緩存的數據量相比,管理實際打算分配給存儲過程和任何種類查詢計劃的緩存量的能力。該成本被計算出來之后,該計劃就會被寫入緩存。
圖 8 顯示計算計劃成本并將其寫入緩存的流程。
圖 8. 將計劃寫入緩存
如果另一個查詢可以重新使用該計劃,則 SQL Server 要再次判定計劃的類型。如果是一個特定計劃,SQL Server 會把成本加 1。這樣,如果特定計劃確實要被重新使用,則它會在緩存中稍作停留,停留時間越長,成本就增加越多。如果該計劃經常被重新使用,則成本會一次增加一個單位地不斷增長,直到增長到其實際編譯成本。該成本和設置的成本一樣高。不過該計劃經常被重復使用;如果同一用戶或其他用戶不斷重新提交完全一樣的 SQL 文本,該計劃就會留在緩存中。
處理來自客戶機的語句時的另一種情況是,查詢是作為 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 版中也應當是最佳的,這個計劃將要么被瑣細計劃優化器找到,要么被基于成本的優化的頭兩個階段中的一個發現。這些規則被有意地組織起來以達到這個目的。這個計劃將很可能由使用單一的索引和使用嵌套循環聯合組成。
摘要 如前所述,SQL Server 的內部機制與結構是一個非常大的主題,遠遠超過了我們能在本文中提供的內容。我們重在直接介紹 SQL Server 與客戶機的交互方式,以及 SQL Server 關系引擎如何處理來自客戶機的請求。我們希望,在了解 SQL Server 如何處理查詢,以及如何和何時編譯或重新編譯它們之后,您就能利用 SQL Server 7.0 的功能和技巧編寫出更好的應用程序。