SqlServer查詢計劃

 

對於一個比較慢的查詢來說,我們通常首先要知道哪些步驟的成本比較高,進而,可以嘗試一些改進的方法。一般來說,如果您不能通過:提高硬件性能或者調整OS,SqlServer的設置之類的方式來解決問題,那麼剩下的可選方法通常也只有以下這些了:
1. 爲【scan】這類操作增加相應字段的索引。
2. 有時重建索引或許也是有效的,具體情形請參考後文。
3. 調整語句結構,引導SqlServer採用其它的查詢方案去執行。
4. 調整表結構(分表或者分區)。

 

Sql Server 查找記錄的方法

 

說到這裏,不得不說SqlServer的索引了。SqlServer有二種索引:聚集索引和非聚集索引。二者的差別在於:【聚集索引】直接決定了記錄的存放位置,或者說:根據聚集索引可以直接獲取到記錄。【非聚集索引】保存了二個信息: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這二種情況分開來保存了,分表或者分區都是不錯的選擇。

如果不能通過增加索引和調整表來解決,那麼可以試試調整語句結構,引導SqlServer採用其它的查詢方案去執行。這種方法要求: 1.對語句所要完成的功能很清楚, 2.對要查詢的數據表結構很清楚, 3.對相關的業務背景知識很清楚。如果能通過這種方法去解決,當然也是很好的解決方法了。不過,有時SqlServer比較智能,即使你調整語句結構,也不會影響它的執行計劃。

如何比較二個同樣功能的語句的性能好壞呢,我建議採用二種方法: 1. 直接把二個查詢語句放在【SQL Server Management Studio】,然後去看它們的【執行計劃】,SqlServer會以百分比的方式告訴你二個查詢的【查詢開銷】。這種方法簡單,通常也是可以參考的,不過,有時也會不準,具體原因請接着往下看(可能索引統計信息過舊)。
2. 根據真實的程序調用,寫相應的測試代碼去調用:這種方法就麻煩一些,但是它更能代表現實調用情況,得到的結果也是更具有參考價值的,因此也是值得的。

Sql Server Join 方式

在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 運算符將從每個輸入獲取一行並將其進行比較。例如,對於內聯接操作,如果行相等則返回。如果行不相等,則廢棄值較小的行並從該輸入獲得另一行。這一過程將重複進行,直到處理完所有的行爲止。

合併聯接操作可以是常規操作,也可以是多對多操作。多對多合併聯接使用臨時表存儲行(會影響效率)。如果每個輸入中有重複值,則在處理其中一個輸入中的每個重複項時,另一個輸入必須重繞到重複項的開始位置。可以創建唯一索引告訴SqlServer不會有重複值。

如果存在駐留謂詞,則所有滿足合併謂詞的行都將對該駐留謂詞取值,而只返回那些滿足該駐留謂詞的行。

合併聯接本身的速度很快,但如果需要排序操作,選擇合併聯接就會非常費時。然而,如果數據量很大且能夠從現有 B 樹索引中獲得預排序的所需數據,則合併聯接通常是最快的可用聯接算法。

3. 【Hash Join】,哈希聯接可以有效處理未排序的大型非索引輸入。它們對複雜查詢的中間結果很有用,因爲: 1. 中間結果未經索引(除非已經顯式保存到磁盤上然後創建索引),而且通常不爲查詢計劃中的下一個操作進行適當的排序。 2. 查詢優化器只估計中間結果的大小。由於對於複雜查詢,估計可能有很大的誤差,因此如果中間結果比預期的大得多,則處理中間結果的算法不僅必須有效而且必須適度弱化。

哈希聯接可以減少使用非規範化。非規範化一般通過減少聯接操作獲得更好的性能,儘管這樣做有冗餘之險(如不一致的更新)。哈希聯接則減少使用非規範化的需要。哈希聯接使垂直分區(用單獨的文件或索引代表單個表中的幾組列)得以成爲物理數據庫設計的可行選項。

哈希聯接有兩種輸入:生成輸入和探測輸入。查詢優化器指派這些角色,使兩個輸入中較小的那個作爲生成輸入。

哈希聯接用於多種設置匹配操作:內部聯接;左外部聯接、右外部聯接和完全外部聯接;左半聯接和右半聯接;交集;聯合和差異。此外,哈希聯接的某種變形可以進行重複刪除和分組,例如 SUM(salary) GROUP BY department。這些修改對生成和探測角色只使用一個輸入。

哈希聯接又分爲3個類型:內存中的哈希聯接、Grace 哈希聯接和遞歸哈希聯接。

內存中的哈希聯接:哈希聯接先掃描或計算整個生成輸入,然後在內存中生成哈希表。根據計算得出的哈希鍵的哈希值,將每行插入哈希存儲桶。如果整個生成輸入小於可用內存,則可以將所有行都插入哈希表中。生成階段之後是探測階段。一次一行地對整個探測輸入進行掃描或計算,併爲每個探測行計算哈希鍵的值,掃描相應的哈希存儲桶並生成匹配項。

Grace 哈希聯接:如果生成輸入大於內存,哈希聯接將分爲幾步進行。這稱爲“Grace 哈希聯接”。每一步都分爲生成階段和探測階段。首先,消耗整個生成和探測輸入並將其分區(使用哈希鍵上的哈希函數)爲多個文件。對哈希鍵使用哈希函數可以保證任意兩個聯接記錄一定位於相同的文件對中。因此,聯接兩個大輸入的任務簡化爲相同任務的多個較小的實例。然後將哈希聯接應用於每對分區文件。

遞歸哈希聯接:如果生成輸入非常大,以至於標準外部合併的輸入需要多個合併級別,則需要多個分區步驟和多個分區級別。如果只有某些分區較大,則只需對那些分區使用附加的分區步驟。爲了使所有分區步驟儘可能快,將使用大的異步 I/O 操作以便單個線程就能使多個磁盤驅動器繁忙工作。

在優化過程中不能始終確定使用哪種哈希聯接。因此,SQL Server 開始時使用內存中的哈希聯接,然後根據生成輸入的大小逐漸轉換到 Grace 哈希聯接和遞歸哈希聯接。
如果優化器錯誤地預計兩個輸入中哪個較小並由此確定哪個作爲生成輸入,生成角色和探測角色將動態反轉。哈希聯接確保使用較小的溢出文件作爲生成輸入。這一技術稱爲“角色反轉”。至少一個文件溢出到磁盤後,哈希聯接中才會發生角色反轉。

說明:您也可以顯式的指定聯接方式,SqlServer會盡量尊重您的選擇。比如你可以這樣寫:inner loop join, left outer merge join, inner hash join
但是,我還是建議您不要這樣做,因爲SqlServer的選擇基本上都是正確的,不信您可以試一下。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章