理解圖形化執行計劃 -- 第3部分:分析執行計劃

理解圖形化執行計劃 -- 第3部分:分析執行計劃


英文原文:

http://www.sqlservercentral.com/articles/Execution+Plans/105810/


對於SQL Server數據庫管理員和開發來說,能夠理解和分析執行計劃是一項非常重要且有益的技能。執行計劃將查詢的預估花銷、索引使用和執行的操作文檔化輸出。所有的信息對於試着加速一個慢查詢來說都是極其重要的。


這篇文章是關於圖形化執行計劃的三部分系列文章之一。第1部分解釋了執行計劃是什麼,並討論了預估和實際執行計劃的不同。第2部分顯示瞭如何創建預估和實際執行計劃。最後,第3部分深入一個簡單的圖形化執行計劃,並討論了一些最普遍的查詢中的操作。


閱讀執行計劃


讓我們看下如何從執行情況和執行計劃中獲得信息。


基礎


圖1從[AdventureWorks2012].[Person].[Address]表上的一個簡單查詢顯示預估的執行計劃。如你所見,它由表明操作和關聯不同操作的箭頭的圖標組成。箭頭都指向左邊,表明執行計劃從右邊開始並向左運行。按時間順序閱讀執行計劃,從最右邊的操作開始並向左運行。箭頭的相對層次也表明了多少數據正被從一個操作傳遞到另一個。


clip_image002

圖1 一個簡單的預估執行計劃


在圖2中,頂部操作的箭頭比它下面的兩個操作更瘦。頂部操作因此也輸出更少的行。


clip_image004

圖2 一個稍微複雜的執行計劃


在創建了執行計劃後,就有地方可以看到查詢的總消耗。總消耗位於最後的操作。這就是最左邊圖標的上層,在這裏,就是Select操作。將鼠標放在Select圖標的上面會給你一個關於操作信息的提示框(圖3)。


我們對Estimated Subtree Cost感興趣。總的消耗是一個查詢是否執行很快的更好判斷的相對值。它由I/O消耗和CPU消耗組成。Operator Cost是I/O Cost和CPU Cost之和。在這裏我們的查詢很好的低於1。它有可能執行相對較快。另一方面,一個查詢總消耗上千可能需要相當長時間完成。一些因素影響着預估消耗。不僅是相關表和視圖的行和索引的數量,而且環境因素例如CPU數量和磁盤都被用於計算消耗。


clip_image006

圖3 查看執行計劃總消耗


在檢查了總消耗並得到一個運行時間的相對感覺後,接下來,從右到左快速查看執行的操作。在圖1,首先,有一個聚集索引掃描。一個掃描表名查詢不是非常可選擇性(看Frequent Plan Operations下的Index/Table Scan查看更多細節)。在這個特定情況下,它是一個沒有where從句的產物。我們也有TOP操作,從查詢返回前1000行。最後,有一個在select語句中返回指定列的select操作。


一旦你有一個查詢正在做什麼的整體感受,我們將專注在最高消耗的操作上。在每個圖標的最下面對每個操作執行計劃列出了總消耗的百分比。從圖1到圖2,我們看到聚集索引掃描佔比總消耗的99%。我們想專注在這個操作上。


我們看下這個圖標的提示框(看圖4),你會注意到有四個消耗列出。對於每個操作消耗分爲CPU和IO消耗。Operator Cost是CPU和I/O Cost之和。Sub Tree Cost是當前的Operator Cost加上在它之前的操作的整個操作消耗。如果你從最右邊的操作開始並跟隨箭頭向左,你會看到對每個完成的操作Sub Tree Cost累加。如我們之前查看到的,在左邊最上層的操作的Sub Tree Cost包含了整個查詢的預估的消耗。


clip_image007

圖4 執行計劃消耗


常見的執行計劃操作


下面是一些出現在查詢計劃裏的最常用的操作。


Index/Table Seek -- 當執行seek,SQL Server可以在索引裏有效查找特定值或值的一個可選擇範圍。使用圖書館類比,你使用圖書館的電腦查找一本書的位置,並獲得這本書的位置。


Index/Table Scan -- 在scan操作中,SQL Server通讀整個表或索引。再次使用圖書館類比,相當於查找圖書館中的每一本知道找到你想要的書。試着在大型的大學圖書館這麼做!Scan表明了搜索標準沒有達到足夠的使用seek操作的選擇性。如果在列上沒有索引被查找或者返回的值的數量在索引中佔百分比很大(低選擇性),查看所有的值會更有效。如果你感覺有指定一個WHERE從句或JOIN足夠保證使用索引,確保列上被索引。


Seek/Scan with Bmk謂詞 -- 當在一個索引查找或掃描的提示框的Seek Predicates部分查看,你有時會看到一個謂詞像Bmknnnn這裏nnnn是數字。這表明SQL Server會創建一個書籤用於Index或RID Lookup。當書籤出現,index seek/scan是一個兩步過程的部分,優化器會在使用書籤創建了一個數據集後,執行聚集索引查找(或表查找,如果沒有聚集索引)。查看關於Key/RID Lookup部分的更多信息。


Joins

SQL Server使用了三種join操作:

  1. Hash Match/Join -- Hash Match或者Join可以用於Join(Hash Join)和Group by的(Hash Match)。在這個操作中,查詢優化器從被關聯的兩個表(如圖形化執行計劃中所見)的上層表構建一個哈希表。這被稱爲構造表。低層表(被稱爲探測表)的每行然後搜索構造表匹配數據。在一個Group By的情況下,之前操作的結果被用於構造表和探測表。這類Join效率的關鍵是構造表的大小和服務器的可用內存數量。如果足夠小,優化器將在內存中創建構造表。如果可能,Hash Match將會相當快。另一方面,如果構造表相當大,處理過程編程一個嵌套循環並且非常慢。當這發生的時候,Hash Match的消耗是執行計劃的重要的百分比,你應該使得查詢更具選擇性或者考慮增加一個索引。Hash Join和Match會在執行前等待有足夠的內存。

  2. Merge Join -- 當上層表(如圖形化執行計劃中所示)在Join中很大,Merge Join將會是最快的Join類型。這個Join的消耗與兩個表的行數總和相關(#上層表的行數 + #下層表的行數)。Merge Join的效率的關鍵是關聯的兩個表必須在關聯列上已排序。如果表還沒有排序,優化器將會首先排序表。在這裏,你會在Merge Join操作器之前直接看到一個排序操作器。排序操作是非常消耗性能的,因此如果一個執行計劃在Merge Join之前顯示了一個有高消耗排序操作器,你可能想在關聯列添加索引。

  3. Nested Loop -- 嵌套循環關聯的消耗和兩個錶行數(#上層表的行數 * #下層表的行數)的乘積有關。雖然這個Join沒有Merge Join有效率,當表沒有排序時它的整體消耗比Merge Join低,當上層表很大時它的整體消耗比比Hash Join低。再則,如果操作的消耗在執行計劃中佔很高百分比,那麼讓你的查詢更具選擇性或者添加一個索引。


Key/RID Lookup -- 當在一個非聚集索引上執行seek或scan時發生lookup,並且所有的數據不包含在索引中。當這種情況發生時,如果存在聚集索引,在聚集索引上發生Key Lookup,否則如果沒有聚集索引,執行行標識符(RID)Lookup。Lookup是昂貴的操作,如果可能應該避免。爲了消除Lookup,將Select語句中的列添加包含在非聚集索引中的include從句中,如果可能。要是你有很多列在Select語句中,不可能將所有都包含進去,又怎樣呢?接下來最應該做的事是使得非聚集索引比當前正使用的索引更具選擇性。那就是說,如果你不能移除Lookup,試着減少傳遞給Lookup的行數。


Compute Scalar -- 該操作執行計算處理一個單一值。通常是標量函數、算術計算或字符串聯接的結果。警告,優化器不能預估標量或多語句表值函數的執行計劃。當這些函數之一出現了性能低下,嘗試將它轉換爲一個內聯表值函數。


Concatenation -- 對字符串聯接不操作;而是對於數據集合的聯接。最常用於在UNION ALL操作中國聯接數據。


Sort -- sort操作只將非排序數據作爲輸入並輸出一個排序集合。排序在大型數據集合上是非常消耗性能的。排序也需要在執行前等待內存足夠多。有高排序消耗的執行計劃應該被檢查用於優化。


Parallelism -- 如果SQL Server所在的機器上有不止一個處理器,可選的在多個處理器之間拆分操作。事實上,如果有多個處理器,優化器會創建兩個執行計劃,一個用於並行而一個沒有。SQL Server然後決定哪個最後可能消耗最短的時間。Parallelism致力於將數據分割在多個處理器間覆蓋操作併合並結果。如果並行操作消耗很高,你可以在查詢上使用MAXDOP查詢提示[OPTION (MAXDOP 1) ]強制使用一個處理器。


對於操作的完整列表,查看TechNet文章Showplan邏輯和物理操作參考


總結


在查詢優化中閱讀圖形化執行計劃是一個非常有用的技能。該系列文章介紹了這個主題。我歡迎你對後續文章的評論。如果你也想閱讀更多這個主題的內容,Grant Fritchey的書SQL Server執行計劃提供了最好的最廣泛的涵蓋內容。


參考


.分析查詢,TechNet Library -- http://technet.microsoft.com/en-us/library/ms191227(v=SQL.105).aspx


.分析慢查詢的清單,TechNet Library -- http://technet.microsoft.com/en-us/library/ms177500(v=sql.105).aspx


.Fritchey,Grant(2008),SQL Server執行計劃,Simple Talk出版(本文使用2008版,2013年出版了第2版)

.Showplan邏輯和物理操作參考,TechNet Library -- http://technet.microsoft.com/en-us/library/ms191158.aspx


譯者補充:


淺談SQL Server中的三種物理連接操作

https://msdn.microsoft.com/zh-cn/library/dn144699.aspx


表值函數

表值函數提供強大的結果集生成能力。它可以在查詢內部表或視圖允許的任何地方使用。表值函數在使用上比返回一個結果集的存儲過程更靈活,因爲函數的結果集可以聯接到查詢中的其他表。

SQL Server中有兩種表值函數。內聯表值函數在概念上與帶參數的視圖類似。多語句表值函數允許多條語句在表變量中創建結果集來返回。


1. 內聯表值函數

創建內聯表值函數很簡單。內聯表值函數的內容是一條帶參數的SELECT語句。返回數據類型永遠是表,不過返回表的結構由SELECT語句的結構來定義。下面是內聯表值函數的一個例子,檢索給定CustomerID的商品銷售總量。

USE AdventureWorks2008;
GO
CREATE FUNCTION Sales.ufnSalesByCustomer (@CustomerID int)
RETURNS TABLE
AS
RETURN
(
SELECT P.ProductID, P.Name, SUM(SD.LineTotal) AS Total
FROM Production.Product AS P
JOIN Sales.SalesOrderDetail AS SD
ON SD.ProductID = P.ProductID
JOIN Sales.SalesOrderHeader AS SH
ON SH.SalesOrderID = SD.SalesOrderID
WHERE SH.CustomerID = @CustomerID
GROUP BY P.ProductID, P.Name
);
GO

注意,函數體由一條RETURN語句組成。使用這個函數的一個例子如下所示:

SELECT * FROM Sales.ufnSalesByCustomer(30052);

內聯表值函數功能強大,在要求參數化查詢的情況下值得考慮。它們在結果集如何使用上提供更多的靈活性。


2. 多語句表值函數

多語句表值函數允許多條語句來創建表的內容。多語句表值函數可以用來替換使用多個步驟來構建結果集的存儲過程。

多語句表值函數允許開發人員使用多個步驟動態地填充表,這一點與存儲過程類似,不過它們可以在SELECT語句中像表那樣被引用。

使用多語句表值函數時,表的結構必須在函數頭定義。要爲表使用一個變量名,並且所有修改數據的操作只能引用表變量。

下面的例子是一個函數,類似上一節中創建的ufnSalesByCustomer。首先創建表變量,然後使用剛纔創建的標量函數來更新表變量,讓它包含總的存貨清單。創建函數的語句如下所示:

USE AdventureWorks2008;
GO
CREATE FUNCTION Sales.ufnSalesByCustomerMS (@CustomerID int)
RETURNS @table TABLE
( ProductID int PRIMARY KEY NOT NULL,
ProductName nvarchar(50) NOT NULL,
TotalSales numeric(38,6) NOT NULL,
TotalInventory int NOT NULL )
AS
BEGIN
INSERT INTO @table
SELECT P.ProductID, P.Name, SUM(SD.LineTotal) AS Total, 0
FROM Production.Product AS P
JOIN Sales.SalesOrderDetail SD ON SD.ProductID = P.ProductID
JOIN Sales.SalesOrderHeader SH ON SH.SalesOrderID = SD.SalesOrderID
WHERE SH.CustomerID = @CustomerID
GROUP BY P.ProductID, P.Name;
UPDATE @table
SET TotalInventory = dbo.ufnGetTotalInventoryStock(ProductID);
RETURN;
END;

執行這個函數與執行前面的內聯函數一樣:

SELECT * FROM Sales. ufnSalesByCustomerMS (30052);

-------------------------------------------------------------------------------------

表值函數和標量值函數的不同是 表值函數是返回一個Table類型 Table類型相當與一張存儲在內存中的一張虛擬表.

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