閱讀查詢計劃:SQL Server索引級別9

本文是「Stairway系列:SQL Server索引的階梯」的一部分 索引是數據庫設計的基礎,並告訴開發人員使用數據庫關於設計者的意圖。不幸的是,當性能問題出現時,索引每每被添加爲過後考慮。這裏最後是一個簡單的系列文章,應該使他們快速地使任何數據庫專業人員「快速」 在整個階段,咱們常常說某個查詢以某種方式執行,咱們引用生成的查詢計劃來支持咱們的陳述。Management Studio顯示的估計和實際查詢計劃能夠幫助您肯定索引的收益或缺少。所以,這個級別的目的是讓您充分了解查詢計劃,您能夠: •當你閱讀這個階梯時,驗證咱們的斷言。 •肯定您的索引是否有益於您的查詢。 有許多關於閱讀查詢計劃的文章,其中包括MSDN庫中的一些文章。這裏咱們不打算擴大或取代它們。事實上,咱們會在這個層面提供其中的許多連接/參考。顯示圖形執行計劃(http://msdn.microsoft.com/zh-cn/library/ms178071.aspx)是一個很好的開始。其餘有用的資源包括Grant Fritchey的書,SQL Server執行計劃(以電子書形式免費提供)和Fabiano Amorim關於在查詢計劃輸出中找到的各類運算符的Simple-Talk文章系列(http://www.simple-talk .COM /做家/法比亞諾 - 阿莫林/)。 圖形查詢計劃 查詢計劃是SQL Server執行查詢的一組指令。 SQL Server Management Studio將以文本,圖形或XML格式顯示查詢計劃。例如,考慮如下簡單的查詢:數據庫

SELECT LastName, FirstName, MiddleName, Title 數據庫設計

FROM Person.Contact編輯器

WHERE Suffix = 'Jr.'函數

ORDER BY Title工具

這個查詢的計劃能夠當作如圖1所示。性能

圖1 - 圖形格式的實際查詢計劃 或者,它能夠被視爲文本:優化

|--Sort(ORDER BY:([AdventureWorks].[Person].[Contact].[Title] ASC)) |--Clustered Index Scan(OBJECT:([AdventureWorks].[Person].[Contact].[PK_Contact_ContactID]), WHERE:([AdventureWorks].[Person].[Contact].[Suffix]=N'Jr.'))spa

或者做爲一個XML文檔,像這樣開始:設計

查詢計劃的顯示能夠請求以下: •要請求圖形查詢計劃,請使用Management Studio的SQL編輯器工具欄,它具備「顯示估計執行計劃」和「包括實際執行計劃」按鈕。 「顯示估計執行計劃」選項當即顯示所選TSQL代碼的查詢計劃圖,而不執行查詢。 「包括實際執行計劃」按鈕是一個開關,一旦您選擇了此選項,您執行的每一個查詢批次都將顯示新查詢計劃圖表以及結果和消息。這個選項能夠在圖1中看到。 •要請求文本查詢計劃,請使用SET SHOWPLAN_TEXT ON語句。打開文本版本將關閉圖形版本,不會執行任何查詢。 •要查看XML版本,請右鍵單擊圖形版本,而後從上下文菜單中選擇「顯示執行計劃XML」。對於這個級別的其他部分,咱們將重點放在圖形視圖上,由於它一般提供對計劃的最快理解。對於查詢計劃,一張圖片一般賽過千言萬語。 閱讀圖形查詢計劃圖形查詢計劃一般從右到左讀取;最右邊的圖標表示數據收集流中的第一步。這一般是訪問堆或索引。你不會看到這裏使用的單詞表;相反,您將看到聚簇索引掃描或堆掃描。這是首先看看哪些索引,若是有的話,正在使用。 圖形查詢計劃中的每一個圖標表明一個操做。有關可能的圖標的其餘信息,請參閱http://msdn.microsoft.com/zh-cn/library/ms175913.aspx上的圖形執行計劃圖標 鏈接操做的箭頭表示行,從一個操做流出並進入下一個操做。 將鼠標放在圖標或箭頭上會致使顯示其餘信息。 不要把操做看成一個步驟,由於這意味着一個操做必須在下一個操做開始以前完成。這不必定是真的。例如,當WHERE子句被評估時,也就是說,當一個Filter操做被執行時,行被一次評估一個;不是一次所有。在下一行到達過濾器操做以前,行能夠移動到下一個操做。另外一方面,排序操做必須在第一行移動到下一個操做以前所有完成。 使用一些額外的信息 圖形查詢計劃顯示兩個不屬於計劃自己的可能有用的信息;建議的指標和每一個操做的相對成本。 在上面的示例中,建議的索引(以綠色顯示並按空間要求截斷)建議在聯繫人表的後綴列上使用非聚簇索引;包括標題,名字,中間名和姓氏的列。 這個計劃的每一個操做的相對成本告訴咱們,排序操做是總成本的5%,而表掃描是95%的工做。所以,若是咱們想提升這個查詢的性能,咱們應該解決表掃描,而不是排序;這就是爲何建議索引。若是咱們建立推薦的索引,像這樣:3d

CREATE NONCLUSTERED INDEX IX_Suffix ON Person.Contact

(

Suffix

)

INCLUDE ( Title, FirstName, MiddleName, LastName )

而後從新運行查詢,咱們的讀數從569降到3; 而下面顯示的新查詢計劃顯示了緣由。

新的非彙集索引(索引鍵爲Suffix)具備「WHERE Suffix ='Jr.」條目彙集在一塊兒; 所以,檢索數據所需IO的減小。 所以,與以前計劃中的排序操做相同的排序操做如今佔查詢總成本的75%以上,而不是僅僅是原來成本的5%。 所以,最初的計劃須要75/5 = 15倍的工做量來收集與當前計劃相同的信息。 因爲咱們的WHERE子句只包含一個等號運算符,因此咱們能夠經過將Title列移入索引鍵來改進咱們的索引,以下所示:

IF  EXISTS (SELECT * FROM sys.indexes

WHERE OBJECT_ID = OBJECT_ID(N'Person.Contact')

 

AND name = N'IX_Suffix')

DROP INDEX IX_Suffix ON Person.Contact

CREATE NONCLUSTERED INDEX IX_Suffix ON Person.Contact

(

Suffix, Title

)

INCLUDE ( FirstName, MiddleName, LastName )

如今,所需的條目仍然彙集在索引內,而且在每一個集羣內,它們都是按照請求的順序; 如新查詢計劃所示,如圖2所示。

 

圖2-重建非彙集索引後的查詢計劃

 

該計劃如今顯示,排序操做再也不須要。 在這一點上,咱們能夠放棄咱們很是有利的覆蓋指數。 這將恢復聯繫人表格的方式,當咱們開始時, 當咱們進入咱們的下一個主題時,這是咱們但願的狀態。

 

查看並行流

 

若是兩行能夠並行處理,它們將在圖形顯示中上下顯示。 箭頭的相對寬度表示在每一個流中正在處理的行數。 例如,如下加入,擴展了之前的查詢以包含銷售信息:

 

SELECT C.LastName, C.FirstName, C.MiddleName, C.Title

 

, H.SalesOrderID, H.OrderDate

 

FROM Person.Contact C

 

JOIN Sales.SalesOrderHeader H ON H.ContactID = C.ContactID

 

WHERE Suffix = 'Jr.'

 

ORDER BY Title

 

查詢計劃如圖3所示。

 

圖3 - JOIN的查詢計劃 快速查看計劃告訴咱們一些事情: •兩張桌子同時被掃描。 •大部分工做用於掃描表格。 •出來的更多行或SalesOrderHeader表比出Contact表更多。 •兩個表格沒有彙集成相同的序列; 所以將每一個SalesOrderHeader行與其聯繫人行進行匹配將須要額外的努力。 在這種狀況下,使用哈希匹配操做。 (關於哈希的更多信息。) •排序所選行所需的工做量能夠忽略不計。 即便是單獨的行流也能夠分解成單獨的較少行的流,以利用並行處理。 例如,若是咱們將上述查詢中的WHERE子句更改成WHERE Suffix爲NULL。 更多的行將被返回,95%的Contact行有NULL後綴。 新的查詢計劃反映了這一點,如圖4所示。

 

圖4 - 一個並行查詢計劃 新的計劃也向咱們展現了聯繫人行數的增長,致使匹配和排序操做成爲此查詢的關鍵路徑。若是要提升績效,就要先攻擊這兩個行動。再次,包含列的索引將有所幫助。 像大多數鏈接同樣,咱們的例子經過外鍵/主鍵關係鏈接兩個表。其中的一個表Contact(聯繫人)按ContactID進行排序,ContactID也剛好是其主鍵。在另外一個表中,SaleOrderHeader,ContactID是一個外鍵。因爲ContactID是一個外鍵,所以ContactID訪問的SaleOrderHeader數據請求(例如咱們的聯接示例)多是常見的業務需求。這些請求將受益於ContactID上的索引。 不管什麼時候索引一個外鍵列,老是問本身,若是有的話,列應該做爲包含列添加到索引中。在咱們的例子中,咱們只有一個查詢,而不是一系列的查詢來支持。所以,咱們惟一包含的列將是OrderDate。爲了支持針對SaleOrderHeader表的一系列面向ContactID的查詢,咱們會根據須要在索引中包含更多的SaleOrderHeader列以支持這些附加查詢。 咱們的CREATE INDEX語句是:

CREATE NONCLUSTERED INDEX IX_ContactID ON Sales.SalesOrderHeader

(

ContactID

)

INCLUDE ( OrderDate )

而執行咱們的SalesOrderHeader和Contact信息鏈接的新計劃如圖5所示。

 

圖5 - 計劃在每一個表上使用支持索引的JOIN查詢 由於兩個輸入流如今都由鏈接謂詞列ContactID進行排序;查詢的JOIN部分能夠在不分割流的狀況下完成,也不須要散列;從而將工做負荷的26 + 5 + 3 = 34%減小到工做負荷的4%。 排序,推送和散列 許多查詢操做要求在執行操做以前將數據分組。這些包括DISTINCT,UNION(意味着不一樣),GROUP BY(及其各類聚合函數)和JOIN。一般,SQL Server將使用如下三種方法之一來實現這個分組,第一個方法須要您的幫助: •愉快地發現數據已經預先分類到分組序列中。 •經過執行散列操做對數據進行分組。 •將數據分類到分組順序中。 預分類 索引是您預測數據的方式;即以常常須要的順序向SQL Server提供數據。這就是爲何建立非聚簇索引(每一個都包含列)都使咱們之前的例子受益。實際上,若是將鼠標放在最近查詢中的「合併鏈接」圖標上,則會使用兩個適當排序的輸入流匹配行,並利用它們的排序順序。會出現。這會通知您兩個表/索引的行使用內存和處理器時間的絕對最小值進行鏈接。適當的排序輸入是一個很棒的短語,當鼠標懸停在查詢計劃圖標上時,它會驗證您選擇的索引。 哈希 若是傳入數據的順序不合適,SQL Server可能會使用散列操做對數據進行分組。哈希是一種可使用大量內存的技術,但一般比分類更有效。在執行DISTINCT,UNION和JOIN操做時,散列與排序相比有一個優點,即單個行能夠傳遞到下一個操做,而沒必要等待全部傳入行被散列。可是,在計算分組聚合時,必須先讀取全部輸入行,而後才能將任何聚合值傳遞給下一個操做。 散列信息所需的內存量與所需組的數量直接相關。所以,須要散列來解決:

SELECT Gender, COUNT(*)

FROM NewYorkCityCensus

GROUP BY Gender

只須要不多的記憶,由於只會有兩組; 女性和男性,不管輸入行的數量。 另外一方面:

SELECT LastName, FirstName, COUNT(*)

FROM NewYorkCityCensus

GROUP BY LastName, FirstName

會致使大量的羣體,每一個羣體都須要本身的記憶空間;可能消耗太多內存,哈希成爲解決查詢的不良技術。 有關查詢計劃散列的更多信息,請訪問http://msdn.microsoft.com/en-us/library/ms189582.aspx。 排序 若是數據沒有被預分類(索引),而且若是SQL Server認爲哈希不能有效地完成,SQL Server將對數據進行排序。這一般是最不可取的選擇。所以,若是在計劃的早期出現「排序」圖標,請檢查是否能夠改進索引。若是Sorticon出如今計劃末尾附近,這可能意味着SQL Server將最終輸出按ORDER BY子句所請求的順序排序;而且該序列與用於解析查詢的JOIN,GROUP BY和UNION的序列不一樣。一般狀況下,你能夠作些什麼來避免這種狀況。 結論 查詢計劃顯示SQL Server打算使用或已經使用的方法來執行查詢。它經過詳細描述將要使用的操做,從操做到操做的行的流程以及涉及的並行性來實現。您將此信息視爲文本,圖形或XML顯示。 •圖形計劃顯示每一個操做的相對工做量。 •圖形計劃可能會建議一個索引,以提升查詢的性能。 •瞭解查詢計劃將幫助您評估和優化索引設計

相關文章
相關標籤/搜索