原文連接: http://www.sqlservercentral.com/articles/Stairway+Series/72441/
sql
閱讀查詢計劃:通往SQL Server索引級別9的階梯數據庫
大衛•杜蘭特2011/10/05服務器
該系列數據庫設計
本文是樓梯系列的一部分:SQL Server索引的階梯函數
索引是數據庫設計的基礎,並告訴開發人員使用數據庫很是瞭解設計器的意圖。不幸的是,當性能問題出現時,索引經常被添加到過後。這裏最後是一個簡單的系列文章,它應該能讓任何數據庫專業人員快速「跟上」他們的步伐工具
在整個樓梯中,咱們常常聲明某個查詢以某種方式執行;咱們引用生成的查詢計劃來支持咱們的語句。管理工做室的估計和實際查詢計劃的顯示能夠幫助您肯定索引的優勢或不足。所以,這個級別的目的是讓您充分了解查詢計劃,您能夠:sqlserver
當您閱讀這段樓梯時,驗證咱們的斷言。性能
肯定您的索引是否有利於您的查詢。優化
有許多關於閱讀查詢計劃的文章,包括MSDN庫中的一些文章。咱們的目的不是擴大或取代它們。事實上,咱們將在這個層面上爲他們中的許多人提供連接/參考。一個很好的起點是圖形顯示執行計劃(http://msdn.microsoft.com/en-us/library/ms178071.aspx)。其餘有用的資源包括Grant Fritchey的書,SQL Server執行計劃(免費提供電子書格式),還有Fabiano Amorim的一系列關於查詢計劃輸出中發現的操做符的簡單討論文章(http://www.simple-talk.com/author/fabiano - amorim/)。設計
圖形查詢計劃
查詢計劃是SQL服務器執行查詢的指令集。SQL Server Management Studio將以文本、圖形或XML格式爲您顯示查詢計劃。例如,考慮如下簡單查詢:
SELECT LastName, FirstName, MiddleName, Title 
FROM Person.Contact
WHERE Suffix = 'Jr.'
ORDER BY Title
圖1 -圖形格式的實際查詢計劃
或者,它能夠被視爲文本:
|——排序(按:[AdventureWorks]。[人] (聯繫) [標題]ASC))
|——彙集索引
掃描(對象:([AdventureWorks],[人] [聯繫] [PK_Contact_ContactID]),
地點:([AdventureWorks],[人] (接觸) (後綴)= N 'Jr。'))
或者做爲一個XML文檔,開始這樣:
查詢計劃的顯示可被要求以下:
要請求圖形化查詢計劃,請使用Management Studio的SQL Editor工具欄,該工具欄有「顯示估計執行計劃」和「包含實際執行計劃」按鈕。「顯示估計執行計劃」選項將當即顯示所選的TSQL代碼的查詢計劃圖,而不執行查詢。「包含實際執行計劃」按鈕是一個開關,一旦你選擇了這個選項,你執行的每個查詢批量都會在一個新標籤中顯示你的查詢計劃圖,以及結果和消息。這個選項如圖1所示。
要請求文本查詢計劃,請使用SET SHOWPLAN_TEXT語句。打開文本版本將會關閉圖形化版本,不會執行任何查詢。
閱讀圖形查詢計劃
圖形化查詢計劃一般從右到左讀取;最右邊的圖標表明數據收集流中的第一步。這一般是訪問堆或索引。您將看不到這裏使用的單詞表;相反,您將看到集羣索引掃描或堆掃描。這是第一個查看哪些索引(若是有的話)正在使用的地方。
圖形化查詢計劃中的每一個圖標表示一個操做。附加信息的可能的圖標,看到圖形在http://msdn.microsoft.com/en-us/library/ms175913.aspx上執行計劃圖標
鏈接操做的箭頭表示行,從一個操做流到下一個操做。
將鼠標放在圖標或箭頭上,將會顯示更多信息。
不要將操做視爲步驟,由於這意味着必須在下一次操做開始以前完成一個操做。這並不必定是真的。例如,當一個WHERE子句被評估時,也就是說,當執行一個篩選器操做時,會一次對行進行評估;並非全部的。行能夠移動到下一個操做,而後在隨後一行到達過濾器操做。另外一方面,排序操做必須在第一行移動到下一個操做以前完整地完成。
使用一些額外的信息
圖形化查詢計劃顯示了兩個可能有用的信息,這些信息不是計劃自己的一部分;建議指標及每次操做的相對成本。
在上面的例子中,建議的索引,以綠色表示,並被空間需求截斷,建議在聯繫人表的後綴列上有一個非彙集索引;包含標題、FirstName、MiddleName和LastName列。
這個計劃的每一個操做的相對成本告訴咱們排序操做佔總成本的5%,而表掃描是95%的工做。所以,若是咱們想要提升這個查詢的性能,咱們應該處理表掃描,而不是排序;這就是爲何要提出一個指數。若是咱們建立推薦索引,以下所示:
CREATE NONCLUSTERED INDEX IX_Suffix ON Person.Contact
(
Suffix
)
INCLUDE ( Title, FirstName, MiddleName, LastName )
而後從新運行查詢,咱們的讀數從569降低到3;下面顯示的新查詢計劃說明了緣由。
新的非彙集索引,其索引鍵的後綴,有「WHERE後綴= 'Jr .」。」條目彙集在一塊兒;所以,IO的還原須要檢索數據。結果是,排序操做,與以前的計劃相同的排序操做,如今佔查詢總成本的75%,而不是它所花費的5%。所以,最初的計劃須要75 / 5 = 15倍的工做量,以收集與當前計劃相同的信息。
因爲咱們的WHERE子句只包含一個等式運算符,因此咱們能夠經過將標題列移動到索引鍵來提升索引值,例如:
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 -一個鏈接的查詢計劃
快速瀏覽一下計劃告訴咱們一些事情:
兩張桌子同時掃描。
大部分的工做都花在了掃描表格上。
出現更多的行或SalesOrderHeader表,而不是從表中取出。
這兩張表沒有按相同的順序排列;所以,將每一個SalesOrderHeader與它的聯繫人行匹配將須要額外的努力。在這種狀況下,使用散列匹配操做。(稍後將詳細介紹散列)。
排序選定行所需的工做量能夠忽略不計。
即便單獨的行流也能夠被分割成不一樣的流,每一行均可以使用並行處理。例如,若是咱們將上述查詢中的WHERE子句更改成NULL。
將會返回更多的行,由於95%的聯繫人行有一個NULL後綴。新的查詢計劃反映了這一點,如圖4所示。
圖4 -一個並行查詢計劃
新計劃還向咱們代表,愈來愈多的接觸行致使匹配和排序操做成爲該查詢的關鍵路徑。若是咱們須要改進它的性能,咱們必須首先攻擊這兩個操做。一樣,包含列的索引也會有所幫助。
與大多數鏈接同樣,咱們的示例經過外鍵/主鍵關係鏈接兩個表。其中一個表,Contact,是由ContactID進行排序的,這也是它的主鍵。在另外一個表SaleOrderHeader中,ContactID是一個外鍵。由於ContactID是一個外鍵,因此對於ContactID訪問的SaleOrderHeader數據的請求,例如咱們的join示例,多是一個常見的業務需求。這些請求將受益於ContactID的索引。
每當索引外鍵列時,老是要問本身,若是有的話,應該將列添加到索引中。在咱們的例子中,咱們只有一個查詢,而不是支持的查詢系列。所以,咱們僅包含的列將是OrderDate。爲了支持針對SaleOrderHeader表的面向對象的查詢系列,咱們將在須要時包含更多的SaleOrderHeader列,以支持這些額外的查詢。
咱們的CREATE INDEX語句是:
CREATE NONCLUSTERED INDEX IX_ContactID ON Sales.SalesOrderHeader
(
ContactID
)
INCLUDE ( OrderDate )
圖5 -每一個表上有一個支持索引的鏈接查詢計劃
由於如今兩個輸入流都由鏈接謂詞列進行排序,ContactID;查詢的鏈接部分能夠在不分割流和不進行哈希的狀況下完成;所以,減小26 + 5 + 3 = 34%的工做負載降低到工做負載的4%。
排序、預分類和散列
許多查詢操做要求在執行操做以前將數據分組。這些包括不一樣的、聯合的(意味着不一樣的)、組(以及它的各類聚合函數),並加入。一般,SQL Server將使用三種方法中的一種來實現這個分組,第一個方法須要您的幫助:
很高興地發現數據已經被按分組順序顯示出來了。
經過執行散列操做來分組數據。
將數據排序到分組序列中。
預分類
索引是您進行數據顯示的方式;也就是說,在常常須要的序列中向SQL Server提供數據。這就是爲何建立非彙集索引(每一個包含包含列)的緣由,使咱們之前的例子受益。實際上,若是您將鼠標放在最近查詢中的Merge Join圖標上,那麼這個短語匹配兩個適當排序的輸入流中的行,利用它們的排序順序。就會出現。這告訴您兩個表/索引的行使用了絕對最小的內存和處理器時間。適當的排序輸入是一個很好的短語,當鼠標在查詢計劃圖標上進行鼠標操做時,它能夠驗證您選擇的索引。
哈希
若是傳入的數據不是理想的序列,那麼SQL Server可能會使用散列操做來分組數據。哈希是一種可使用大量內存的技術,但一般比排序更有效。在執行不一樣的、聯合和鏈接操做時,哈希比在單獨的行中排序能夠傳遞到下一個操做,而沒必要等待全部傳入的行被散列。然而,在計算分組集合時,必須讀取全部輸入行,而後才能將任何聚合值傳遞給下一個操做。
散列信息所需的內存數量與所需的組數直接相關。所以,hashing須要解決:
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子句所請求的序列;這個序列與用於解析查詢的鏈接、組BYs和union的序列不一樣。一般,在這一點上,你幾乎沒有辦法避免這種狀況。
結論
查詢計劃向您展現了SQL Server打算使用或使用的方法來執行查詢。它經過詳細描述將要使用的操做、從操做到操做的行流程以及所涉及的並行性來實現。
您將此信息視爲文本、圖形或XML顯示。
圖形化計劃顯示了每一個操做的相對工做負載。
圖形化的計劃能夠建議一個索引來提升查詢的性能。
瞭解查詢計劃將幫助您評估和優化您的索引設計。
本文是通往SQL Server索引樓梯的樓梯的一部分