對於SQL Server的優化來講,優化查詢多是很常見的事情。因爲數據庫的優化,自己也是一個涉及面比較的廣的話題, 所以本文只談優化查詢時如何看懂SQL Server查詢計劃。畢竟我對SQL Server的認識有限,若有錯誤,也懇請您在發現後及時批評指正。算法
首先,打開【SQL Server Management Studio】,輸入一個查詢語句看看SQL Server是如何顯示查詢計劃的吧。
說明:本文所演示的數據庫,是我爲一個演示程序專用準備的數據庫, 能夠在此網頁中下載。 數據庫
select v.OrderID, v.CustomerID, v.CustomerName, v.OrderDate, v.SumMoney, v.Finished from OrdersView as v where v.OrderDate >= '2010-12-1' and v.OrderDate < '2011-12-1';
其中,OrdersView是一個視圖,其定義以下:緩存
SELECT dbo.Orders.OrderID, dbo.Orders.CustomerID, dbo.Orders.OrderDate, dbo.Orders.SumMoney, dbo.Orders.Finished, ISNULL(dbo.Customers.CustomerName, N'') AS CustomerName FROM dbo.Orders LEFT OUTER JOIN dbo.Customers ON dbo.Orders.CustomerID = dbo.Customers.CustomerID
對於前一句查詢,SQL Server給出的查詢計劃以下(點擊工具欄上的【顯示估計的執行計劃】按鈕):併發
從這個圖中,咱們至少能夠獲得3個有用的信息:
1. 哪些執行步驟花費的成本比較高。顯然,最右邊的二個步驟的成本是比較高的。
2. 哪些執行步驟產生的數據量比較多。對於每一個步驟所產生的數據量, SQL Server的執行計劃是用【線條粗細】來表示的,所以也很容易地從分辨出來。
3. 每一步執行了什麼樣的動做。 異步
對於一個比較慢的查詢來講,咱們一般要知道哪些步驟的成本比較高,進而,能夠嘗試一些改進的方法。 通常來講,若是您不能經過:提升硬件性能或者調整OS,SQL Server的設置之類的方式來解決問題,那麼剩下的可選方法一般也只有如下這些了:
1. 爲【scan】這類操做增長相應字段的索引。
2. 有時重建索引或許也是有效的,具體情形請參考後文。
3. 調整語句結構,引導SQL Server採用其它的查詢方案去執行。
4. 調整表結構(分表或者分區)。 數據庫設計
下面再來講說一些很重要的理論知識,這些內容對於執行計劃的理解是頗有幫助的。ide
說到這裏,不得不說SQL Server的索引了。SQL Server有二種索引:彙集索引和非彙集索引。兩者的差異在於:【彙集索引】直接決定了記錄的存放位置, 或者說:根據彙集索引能夠直接獲取到記錄。【非彙集索引】保存了二個信息:1.相應索引字段的值,2.記錄對應彙集索引的位置(若是表沒有彙集索引則保存記錄指針)。 所以,若是能經過【彙集索引】來查找記錄,顯然也是最快的。 函數
SQL Server 會有如下方法來查找您須要的數據記錄:
1. 【Table Scan】:遍歷整個表,查找全部匹配的記錄行。這個操做將會一行一行的檢查,固然,效率也是最差的。
2. 【Index Scan】:根據索引,從表中過濾出來一部分記錄,再查找全部匹配的記錄行,顯然比第一種方式的查找範圍要小,所以比【Table Scan】要快。
3. 【Index Seek】:根據索引,定位(獲取)記錄的存放位置,而後取得記錄,所以,比起前二種方式會更快。
4. 【Clustered Index Scan】:和【Table Scan】同樣。注意:不要覺得這裏有個Index,就認爲不同了。 其實它的意思是說:按彙集索引來逐行掃描每一行記錄,由於記錄就是按彙集索引來順序存放的。 而【Table Scan】只是說:要掃描的表沒有彙集索引而已,所以這二個操做本質上也是同樣的。
5. 【Clustered Index Seek】:直接根據彙集索引獲取記錄,最快! 工具
因此,當發現某個查詢比較慢時,能夠首先檢查哪些操做的成本比較高,再看看那些操做在查找記錄時, 是否是【Table Scan】或者【Clustered Index Scan】,若是確實和這二種操做類型有關,則要考慮增長索引來解決了。 不過,增長索引後,也會影響數據表的修改動做,由於修改數據表時,要更新相應字段的索引。因此索引過多,也會影響性能。 還有一種狀況是不適合增長索引的:某個字段用0或1表示的狀態。例如可能有絕大多數是1,那麼此時加索引根本就沒有意義。 這時只能考慮爲0或者1這二種狀況分開來保存了,分表或者分區都是不錯的選擇。
若是不能經過增長索引和調整表來解決,那麼能夠試試調整語句結構,引導SQL Server採用其它的查詢方案去執行。 這種方法要求: 1.對語句所要完成的功能很清楚, 2.對要查詢的數據表結構很清楚, 3.對相關的業務背景知識很清楚。 若是能經過這種方法去解決,固然也是很好的解決方法了。不過,有時SQL Server比較智能,即便你調整語句結構,也不會影響它的執行計劃。
如何比較二個相同功能的SQL語句的性能好壞呢,我建議採用二種方法: 1. 直接把二個查詢語句放在【SQL Server Management Studio】,而後去看它們的【執行計劃】,SQL Server會以百分比的方式告訴你二個查詢的【查詢開銷】。 這種方法簡單,一般也是能夠參考的,不過,有時也會不許,具體緣由請接着往下看(可能索引統計信息過舊)。
2. 根據真實的程序調用,寫相應的測試代碼去調用:這種方法就麻煩一些,可是它更能表明現實調用狀況, 獲得的結果也是更具備參考價值的,所以也是值得的。
在SQL Server中,每一個join命令,都會在內部執行時採用三種更具體的方式來運行:
1. 【Nested Loops join】,若是一個聯接輸入很小,而另外一個聯接輸入很大並且已在其聯接列上建立了索引, 則索引 Nested Loops 鏈接是最快的聯接操做,由於它們須要的 I/O 和比較都最少。
嵌套循環聯接也稱爲「嵌套迭代」,它將一個聯接輸入用做外部輸入表(顯示爲圖形執行計劃中的頂端輸入),將另外一個聯接輸入用做內部(底端)輸入表。外部循環逐行處理外部輸入表。內部循環會針對每一個外部行執行,在內部輸入表中搜索匹配行。能夠用下面的僞碼來理解:
foreach(row r1 in outer table) foreach(row r2 in inner table) if( r1, r2 符合匹配條件 ) output(r1, r2);
最簡單的狀況是,搜索時掃描整個表或索引;這稱爲「單純嵌套循環聯接」。若是搜索時使用索引,則稱爲「索引嵌套循環聯接」。若是將索引生成爲查詢計劃的一部分(並在查詢完成後當即將索引破壞),則稱爲「臨時索引嵌套循環聯接」。查詢優化器考慮了全部這些不一樣狀況。
若是外部輸入較小而內部輸入較大且預先建立了索引,則嵌套循環聯接尤爲有效。在許多小事務中(如那些隻影響較小的一組行的事務),索引嵌套循環聯接優於合併聯接和哈希聯接。但在大型查詢中,嵌套循環聯接一般不是最佳選擇。
2. 【Merge Join】,若是兩個聯接輸入並不小但已在兩者聯接列上排序(例如,若是它們是經過掃描已排序的索引得到的),則合併聯 接是最快的聯接操做。若是兩個聯接輸入都很大,並且這兩個輸入的大小差很少,則預先排序的合併聯接提供的性能與哈希聯接相近。可是,若是這兩個輸入的大小 相差很大,則哈希聯接操做一般快得多。
合併聯接要求兩個輸入都在合併列上排序,而合併列由聯接謂詞的等效 (ON) 子句定義。一般,查詢優化器掃描索引(若是在適當的一組列上存在索引),或在合併聯接的下面放一個排序運算符。在極少數狀況下,雖然可能有多個等效子句, 但只用其中一些可用的等效子句得到合併列。
因爲每一個輸入都已排序,所以 Merge Join 運算符將從每一個輸入獲取一行並將其進行比較。例如,對於內聯接操做,若是行相等則返回。若是行不相等,則廢棄值較小的行並從該輸入得到另外一行。這一過程將重複進行,直處處理完全部的行爲止。
合併聯接操做能夠是常規操做,也能夠是多對多操做。多對多合併聯接使用臨時表存儲行(會影響效率)。若是每一個輸入中有重複值,則在處理其中一個輸入中的每一個重複項時,另外一個輸入必須重繞到重複項的開始位置。 能夠建立惟一索引告訴SQL Server不會有重複值。
若是存在駐留謂詞,則全部知足合併謂詞的行都將對該駐留謂詞取值,而只返回那些知足該駐留謂詞的行。
合併聯接自己的速度很快,但若是須要排序操做,選擇合併聯接就會很是費時。然而,若是數據量很大且可以從現有 B 樹索引中得到預排序的所需數據,則合併聯接一般是最快的可用聯接算法。
3. 【Hash Join】,哈希聯接能夠有效處理未排序的大型非索引輸入。它們對複雜查詢的中間結果頗有用,由於: 1. 中間結果未經索引(除非已經顯式保存到磁盤上而後建立索引),並且一般不爲查詢計劃中的下一個操做進行適當的排序。 2. 查詢優化器只估計中間結果的大小。因爲對於複雜查詢,估計可能有很大的偏差,所以若是中間結果比預期的大得多,則處理中間結果的算法不只必須有效並且必須適度弱化。
哈希聯接能夠減小使用非規範化。非規範化通常經過減小聯接操做得到更好的性能,儘管這樣作有冗餘之險(如不一致的更新)。哈希聯接則減小使用非規範化的須要。哈希聯接使垂直分區(用單獨的文件或索引表明單個表中的幾組列)得以成爲物理數據庫設計的可行選項。
哈希聯接有兩種輸入:生成輸入和探測輸入。查詢優化器指派這些角色,使兩個輸入中較小的那個做爲生成輸入。
哈希聯接用於多種設置匹配操做:內部聯接;左外部聯接、右外部聯接和徹底外部聯接;左半聯接和右半聯接;交集;聯合和差別。此外,哈希聯接的某種變形能夠 進行重複刪除和分組,例如 SUM(salary) GROUP BY department。這些修改對生成和探測角色只使用一個輸入。
哈希聯接又分爲3個類型:內存中的哈希聯接、Grace 哈希聯接和遞歸哈希聯接。
內存中的哈希聯接:哈希聯接先掃描或計算整個生成輸入,而後在內存中生成哈希表。根據計算得出的哈希鍵的哈希值,將每行插入哈希存儲桶。若是整個生成輸入 小於可用內存,則能夠將全部行都插入哈希表中。生成階段以後是探測階段。一次一行地對整個探測輸入進行掃描或計算,併爲每一個探測行計算哈希鍵的值,掃描相 應的哈希存儲桶並生成匹配項。
Grace 哈希聯接:若是生成輸入大於內存,哈希聯接將分爲幾步進行。這稱爲「Grace 哈希聯接」。每一步都分爲生成階段和探測階段。首先,消耗整個生成和探測輸入並將其分區(使用哈希鍵上的哈希函數)爲多個文件。對哈希鍵使用哈希函數能夠 保證任意兩個聯接記錄必定位於相同的文件對中。所以,聯接兩個大輸入的任務簡化爲相同任務的多個較小的實例。而後將哈希聯接應用於每對分區文件。
遞歸哈希聯接:若是生成輸入很是大,以致於標準外部合併的輸入須要多個合併級別,則須要多個分區步驟和多個分區級別。若是隻有某些分區較大,則只需對那些 分區使用附加的分區步驟。爲了使全部分區步驟儘量快,將使用大的異步 I/O 操做以便單個線程就能使多個磁盤驅動器繁忙工做。
在優化過程當中不能始終肯定使用哪一種哈希聯接。所以,SQL Server 開始時使用內存中的哈希聯接,而後根據生成輸入的大小逐漸轉換到 Grace 哈希聯接和遞歸哈希聯接。
若是優化器錯誤地預計兩個輸入中哪一個較小並由此肯定哪一個做爲生成輸入,生成角色和探測角色將動態反轉。哈希聯接確保使用較小的溢出文件做爲生成輸入。這一技術稱爲「角色反轉」。至少一個文件溢出到磁盤後,哈希聯接中才會發生角色反轉。
說明:您也能夠顯式的指定聯接方式,SQL Server會盡可能尊重您的選擇。好比你能夠這樣寫:inner loop join, left outer merge join, inner hash join
可是,我仍是建議您不要這樣作,由於SQL Server的選擇基本上都是正確的,不信您能夠試一下。
好了,說了一大堆理論東西,再來個實際的例子解釋一下吧。
前面,我給出一張圖片,它反映了SQL Server在執行某個查詢的執行計劃,但它反映的信息可能不太細緻,固然,您能夠把鼠標指標移動某個節點上,會有如下信息出現:
恰好,我裝的是中文版的,上面都是漢字,我也很少說了。我要說的是另外一種方式的執行過程,比這個包含更多的執行信息, 並且是實際的執行狀況。(固然,您也能夠繼續使用圖形方式,在運行查詢前點擊工具欄上的【包括實際的執行計劃】按鈕)
讓咱們再次回到【SQL Server Management Studio】,輸入如下語句,而後執行。
set statistics profile on select v.OrderID, v.CustomerID, v.CustomerName, v.OrderDate, v.SumMoney, v.Finished from OrdersView as v where v.OrderDate >= '2010-12-1' and v.OrderDate < '2011-12-1';
注意:如今加了一句,【set statistics profile on 】,獲得的結果以下:
能夠從圖片上看到,執行查詢後,獲得二個表格,上面的表格顯示了查詢的結果,下面的表格顯示了查詢的執行過程。相比本文的第一張圖片, 這張圖片可能在直觀上不太友好,可是,它能反映更多的信息,並且尤爲在比較複雜的查詢時,可能看起來更容易,由於對於複雜的查詢,【執行計劃】的步驟太多,圖形方式會形成圖形過大,不容易觀察。 並且這張執行過程表格能反映2個頗有價值的數據(前二列)。
仍是來看看這個【執行過程表格】吧。我來挑幾個重要的說一下。
【Rows】:表示在一個執行步驟中,所產生的記錄條數。(真實數據,非預期)
【Executes】:表示某個執行步驟被執行的次數。(真實數據,非預期)
【Stmt Text】:表示要執行的步驟的描述。
【EstimateRows】:表示要預期返回多少行數據。
在這個【執行過程表格】中,對於優化查詢來講,我認爲前三列是比較重要的。對於前二列,我上面也解釋了,意思也很清楚。 前二列的數字也大體反映了那些步驟所花的成本,對於比較慢的查詢中,應該留意它們。 【Stmt Text】會告訴你每一個步驟作了什麼事情。對於這種表格,它所要表達的實際上是一種樹型信息(一行就表示在圖形方式下的一個節點), 因此,我建議從最內層開始去讀它們。作爲示例,我來解釋一下這張表格它所表達的執行過程。
第5行:【Clustered Index Seek(OBJECT:([MyNorthwind].[dbo].[Customers].[PK_Customers]), SEEK:([MyNorthwind].[dbo].[Customers].[CustomerID]=[MyNorthwind].[dbo].[Orders].[CustomerID]) ORDERED FORWARD)】, 意思是說,SQL Server在對錶Customers作Seek操做,並且是按照【Clustered Index Seek】的方式,對應的索引是【PK_Customers】,seek的值來源於[Orders].[CustomerID]
第4行:【Clustered Index Scan(OBJECT:([MyNorthwind].[dbo].[Orders].[PK_Orders]), WHERE:([MyNorthwind].[dbo].[Orders].[OrderDate]>='2010-12-01 00:00:00.000' AND [MyNorthwind].[dbo].[Orders].[OrderDate]<'2011-12-01 00:00:00.000'))】, 意思是說,SQL Server在對錶Customers作Scan操做,即:最差的【表掃描】的方式,緣由是,OrderDate列上沒有索引,因此只能這樣了。
第3行:【Nested Loops(Left Outer Join, OUTER REFERENCES:([MyNorthwind].[dbo].[Orders].[CustomerID]))】, 意思是說,SQL Server把第5行和第4行產生的數據用【Nested Loops】的方式聯接起來,其中Outer表是Orders,要聯接的匹配操做也在第5行中指出了。
第2行:【Compute Scalar(DEFINE:([Expr1006]=isnull([MyNorthwind].[dbo].[Customers].[CustomerName],N'')))】, 意思是說,要執行一個isnull()函數的調用。具體緣由請參考本文前部分中給出視圖定義代碼。
第1行:【SELECT [v].[OrderID],[v].[CustomerID],[v].[CustomerName],[v].[OrderDate],[v].[SumMoney],[v].[Finished] FROM [OrdersView] [v] WHERE [v].[OrderDate]>=@1 AND [v].[OrderDate]<@2】, 一般第1行就是整個查詢,表示它的返回值。
前面一直說到【執行計劃】,既然是計劃,就表示要在具體執行前就能肯定下來的操做方案。那麼SQL Server是如何選擇一個執行計劃的呢? SQL Server怎麼知道何時該用索引或者用哪一個索引呢? 對於SQL Server來講,每當要執行一個查詢時,都要首先檢查這個查詢的執行計劃是否存在緩存中,若是沒有,就要生成一個執行計劃, 具體在產生執行計劃時,並非看有哪些索引可用(隨機選擇),而是會參考一種被稱爲【索引統計信息】的數據。 若是您仔細地看一下前面的執行計劃或者執行過程表格,會發現SQL Server能預估每一個步驟所產生的數據量, 正是由於SQL Server能預估這些數據量,SQL Server才能選擇一個它認爲最合適的方法去執行查詢過程, 此時【索引統計信息】就能告訴SQL Server這些信息。 說到這裏,您是否是有點好奇呢,爲了讓您對【索引統計信息】有個感性的認識,咱們來看看【索引統計信息】是個什麼樣子的。 請在【SQL Server Management Studio】,輸入如下語句,而後執行。
dbcc show_statistics (Products, IX_CategoryID)
獲得的結果以下圖:
首先,仍是解釋一下命令:【dbcc show_statistics】這個命令能夠顯示咱們想知道的【索引統計信息】,它須要二個參數,1. 表名,2. 索引名
再來看看命令的結果,它有三個表格組成:
1. 第一個表格,它列出了這個索引統計信息的主要信息。
列名 | 說明 |
---|---|
Name | 統計信息的名稱。 |
Updated | 上一次更新統計信息的日期和時間。 |
Rows | 表中的行數。 |
Rows Sampled | 統計信息的抽樣行數。 |
Steps | 數據可分紅多少個組,與第三個表對應。 |
Density | 第一個索引列前綴的選擇性(不包括 EQ_ROWS)。 |
Average key length | 全部索引列的平均長度。 |
String Index | 若是爲「是」,則統計信息中包含字符串摘要索引,以支持爲 LIKE 條件估算結果集大小。僅適用於 char、varchar、nchar 和 nvarchar、varchar(max)、nvarchar(max)、text 以及 ntext 數據類型的前導列。 |
2. 第二個表格,它列出各類字段組合的選擇性,數據越小表示重複越性越小,固然選擇性也就越高。
列名 | 說明 |
---|---|
All density | 索引列前綴集的選擇性(包括 EQ_ROWS)。注意:這個值越小就表示選擇性越高。 若是這個值小於0.1,這個索引的選擇性就比較高,反之,則表示選擇性就不高了。 |
Average length | 索引列前綴集的平均長度。 |
Columns | 爲其顯示 All density 和 Average length 的索引列前綴的名稱。 |
3. 第三個表格,數據分佈的直方圖,SQL Server就是靠它預估一些執行步驟的數據量。
列名 | 說明 |
---|---|
RANGE_HI_KEY | 每一個組中的最大值。 |
RANGE_ROWS | 每組數據組的估算行數,不包含最大值。 |
EQ_ROWS | 每組數據組中與最大值相等的行的估算數目。 |
DISTINCT_RANGE_ROWS | 每組數據組中的非重複值的估算數目,不包含最大值。 |
AVG_RANGE_ROWS | 每組數據組中的重複值的平均數目,不包含最大值,計算公式:RANGE_ROWS / DISTINCT_RANGE_ROWS for DISTINCT_RANGE_ROWS > 0 |
爲了能讓您更好的理解這些數據,尤爲是第三組,請看下圖:
當時我在填充測試數據時,故意把CategoryId分爲1到8(10是後來臨時加的),每組填充了78條數據。因此【索引統計信息】的第三個表格的數據也都是正確的, 也正是根據這些統計信息,SQL Server才能對每一個執行步驟預估相應的數據量,從而影響Join之類的選擇。固然了,在選擇Join方式時, 也要參考第二個表格中字段的選擇性。SQL Server在爲查詢生成執行計劃時, 查詢優化器將使用這些統計信息並結合相關的索引來評估每種方案的開銷來選擇最佳的查詢計劃。
再來個例子說明一下統計信息對於查詢計劃的重要性。首先多加點數據,請看如下代碼:
declare @newCategoryId int; insert into dbo.Categories (CategoryName) values(N'Test statistics'); set @newCategoryId = scope_identity(); declare @count int; set @count = 0; while( @count < 100000 ) begin insert into Products (ProductName, CategoryID, Unit, UnitPrice, Quantity, Remark) values( cast(newid() as nvarchar(50)), @newCategoryId, N'個', 100, @count +1, N''); set @count = @count + 1; end go update statistics Products; go
再來看看索引統計信息:
再來看看同一個查詢,但由於查詢參數值不一樣時,SQL Server選擇的執行計劃:
select p.ProductId, t.Quantity from Products as p left outer join [Order Details] as t on p.ProductId = t.ProductId where p.CategoryId = 26; -- 26 就是最新產生的CategoryId,所以這個查詢會返回10W條記錄 select p.ProductId, t.Quantity from Products as p left outer join [Order Details] as t on p.ProductId = t.ProductId where p.CategoryId = 6; -- 這個查詢會返回95條記錄
從上圖能夠看出,因爲CategoryId的參數值不一樣,SQL Server會選擇徹底不一樣的執行計劃。統計信息重要性在這裏體現的很清楚吧。
建立統計信息後,數據庫引擎對列值(根據這些值建立統計信息)進行排序, 並根據這些值(最多 200 個,按間隔分隔開)建立一個「直方圖」。直方圖指定有多少行精確匹配每一個間隔值, 有多少行在間隔範圍內,以及間隔中值的密度大小或重複值的發生率。
SQL Server 2005 引入了對 char、varchar、varchar(max)、nchar、nvarchar、nvarchar(max)、text 和 ntext 列建立的統計信息收集的其餘信息。這些信息稱爲「字符串摘要」,能夠幫助查詢優化器估計字符串模式中查詢謂詞的選擇性。 查詢中有 LIKE 條件時,使用字符串摘要能夠更準確地估計結果集大小,並不斷優化查詢計劃。 這些條件包括諸如 WHERE ProductName LIKE '%Bike' 和 WHERE Name LIKE '[CS]heryl' 之類的條件。
既然【索引統計信息】這麼重要,那麼它會在何時生成或者更新呢?事實上,【索引統計信息】是不用咱們手工去維護的, SQL Server會自動去維護它們。並且在SQL Server中也有個參數來控制這個更新方式:
統計信息自動功能工做方式
建立索引時,查詢優化器自動存儲有關索引列的統計信息。另外,當 AUTO_CREATE_STATISTICS 數據庫選項設置爲 ON(默認值)時, 數據庫引擎自動爲沒有用於謂詞的索引的列建立統計信息。
隨着列中數據發生變化,索引和列的統計信息可能會過期,從而致使查詢優化器選擇的查詢處理方法不是最佳的。 例如,若是建立一個包含一個索引列和 1,000 行數據的表,每一行在索引列中的值都是惟一的, 則查詢優化器將把該索引列視爲收集查詢數據的好方法。若是更新列中的數據後存在許多重複值, 則該列再也不是用於查詢的理想候選列。可是,查詢優化器仍然根據索引的過期分佈統計信息(基於更新前的數據),將其視爲好的候選列。
當 AUTO_UPDATE_STATISTICS 數據庫選項設置爲 ON(默認值)時,查詢優化器會在表中的數據發生變化時自動按期更新這些統計信息。 每當查詢執行計劃中使用的統計信息沒有經過針對當前統計信息的測試時就會啓動統計信息更新。 採樣是在各個數據頁上隨機進行的,取自表或統計信息所需列的最小非彙集索引。 從磁盤讀取一個數據頁後,該數據頁上的全部行都被用來更新統計信息。 常規狀況是:在大約有 20% 的數據行發生變化時更新統計信息。可是,查詢優化器始終確保採樣的行數儘可能少。 對於小於 8 MB 的表,則始終進行完整掃描來收集統計信息。
採樣數據(而不是分析全部數據)能夠將統計信息自動更新的開銷降至最低。 在某些狀況下,統計採樣沒法得到表中數據的精確特徵。可使用 UPDATE STATISTICS 語句的 SAMPLE 子句和 FULLSCAN 子句, 控制按逐個表的方式手動更新統計信息時採樣的數據量。FULLSCAN 子句指定掃描表中的全部數據來收集統計信息, 而 SAMPLE 子句用來指定採樣的行數百分比或採樣的行數
在 SQL Server 2005 中,數據庫選項 AUTO_UPDATE_STATISTICS_ASYNC 提供了統計信息異步更新功能。 當此選項設置爲 ON 時,查詢不等待統計信息更新,便可進行編譯。而過時的統計信息置於隊列中, 由後臺進程中的工做線程來更新。查詢和任何其餘併發查詢都經過使用現有的過時統計信息當即編譯。 因爲不存在等待更新後的統計信息的延遲,所以查詢響應時間可預測;可是過時的統計信息可能致使查詢優化器選擇低效的查詢計劃。 在更新後的統計信息就緒後啓動的查詢將使用那些統計信息。這可能會致使從新編譯緩存的計劃(取決於較舊的統計信息版本)。 若是在同一個顯式用戶事務中出現某些數據定義語言 (DDL) 語句(例如,CREATE、ALTER 和 DROP 語句),則沒法更新異步統計信息。
AUTO_UPDATE_STATISTICS_ASYNC 選項設置於數據庫級別,並肯定用於數據庫中全部統計信息的更新方法。 它只適用於統計信息更新,而沒法用於以異步方式建立統計信息。只有將 AUTO_UPDATE_STATISTICS 設置爲 ON 時, 將此選項設置爲 ON 纔有效。默認狀況下,AUTO_UPDATE_STATISTICS_ASYNC 選項設置爲 OFF。
從以上說明中,咱們能夠看出,對於大表,仍是有可能存在統計信息更新不及時的時候,這時,就可能會影響查詢優化器的判斷了。
有些人可能有個經驗:對於一些慢的查詢,他們會想到重建索引來嘗試解決。其實這樣作是有道理的。 由於,在某些時候一個查詢忽然變慢了,可能和統計信息更新不及時有關,進而會影響查詢優化器的判斷。 若是此時重建索引,就可讓查詢優化器知道最新的數據分佈,天然就能夠避開這個問題。 還記得我前面用【set statistics profile on】顯示的執行過程表格嗎?注意哦,那個表格就顯示每一個步驟的實際數據量和預估的數據量。要不要重建索引,其實咱們能夠用【set statistics profile on】來看一下,若是實際數據量和預估的數據量的差值比較大, 那麼咱們能夠考慮手工去更新統計信息,而後再去試試。
再來講說優化視圖查詢,雖然視圖也是由一個查詢語句定義的,本質上也是一個查詢,但它和通常的查詢語句在優化時,仍是有所區別的。 這裏主要的區別在於,視圖雖然是由一個查詢語句定義的,但若是隻去分析這個查詢定義,可能獲得的意義不大,由於視圖多數時候就不是直接使用, 而是在使用前,會加上where語句,或者放在其它語句中供from子句所使用。下面仍是舉個例子吧,在個人演示數據庫中有個視圖OrdersView,定義代碼前面有。 咱們來看看,若是直接使用這個視圖,會有什麼樣的執行計劃出來:
從這個視圖能夠看出,SQL Server會對錶Orders作全表掃描,應該是很低效的。再來看看下面這個查詢:
從這個執行計劃能夠看出,與上面那個就不同了。前一個查詢中對Orders表的查找是使用【Clustered Index Scan】的方式, 而如今在使用【Clustered Index Seek】的方式了,最右邊二個步驟的成本的百分比也發生了改變。這樣就足以說明,優化視圖時, 最好能根據實際需求,應用不一樣的過濾條件,再來決定如何去優化。
再來一個由三個查詢組成的狀況來看看這個視圖的執行計劃。
select * from dbo.OrdersView where OrderId = 1; select * from dbo.OrdersView where CustomerId = 1; select * from dbo.OrdersView where OrderDate >= '2010-12-1' and OrderDate < '2011-12-1';
很明顯,對於同一個視圖,在不一樣的過濾條件下,執行計劃的差異很明顯。
索引統計信息
http://msdn.microsoft.com/zh-cn/library/ms190397(SQL.90).aspx
查詢優化建議
http://msdn.microsoft.com/zh-cn/library/ms188722(SQL.90).aspx
用於對運行慢的查詢進行分析的清單
http://msdn.microsoft.com/zh-cn/library/ms177500(SQL.90).aspx
邏輯運算符和物理運算符引用
http://msdn.microsoft.com/zh-cn/library/ms191158(SQL.90).aspx