天天看點

性能調優6:Spool 假脫機調優

SQL Server的Spool(假脫機)操作符,用于把前一個操作符處理的資料(又稱作中間結果集)存儲到一個隐藏的臨時結構中,以便在執行過程中重用這些資料。這個臨時結構都建立在tempdb中,通常的結構是工作表(worktable)和工作檔案(workfile)。假脫機運算符會取出表或索引結構中的一部分的資料集,将他們存放在tempdb資料庫的臨時結構裡,我推測:如果這個臨時結構用于存儲表資料,稱作worktable;如果這個臨時結構存儲的是Hash表,稱作workfile。

SQL Server使用Spool操作符的優點是:

  • 資料或中間結果集會被多次重用
  • 使假脫機資料與源資料保持隔離

一,Spool操作的分類

在執行計劃中,Eager Spool和Lazy Spool是邏輯操作符,這兩個邏輯操作符之間的差別是:

  • Eager Spool:一次性把所有資料存儲到臨時結構中,它是一個阻塞性的操作符,這意味着它需要讀取輸入中的所有資料,然後處理完所有的資料行之後,才向下一個操作符傳回結果,也就是說,除非Eager Spool把所有的資料行都處理完成,否則無法通路到這些資料。
  • Lazy Spool:逐行把資料存儲到臨時結構中,它是一個非阻塞性的操作符,這意味着它可以邊讀取資料,邊向下一個操作符輸出資料,也就是說,在Lazy Spool讀取完所有的資料之前,可以通路這些資料。

Spool相關的實體操作符有Spool, Table Spool, Index Spool, Window Spool 和 Row Count Spool,這些實體操作符的作用是:

  • Spool運算符用于把查詢的中間結果集儲存到tempdb資料庫中
  • Row Count Spool運算符掃描輸入,計算現有的行數n,傳回行數n,用于描述輸入的總行數。
  • Index Spool 是把非聚集索引的資料存放到tempdb中的臨時結構中,該運算符掃描輸入的索引結構,把每行的副本放置在隐藏的Spool檔案中(存儲在tempdb資料庫中的worktable,且隻在查詢的生命周期記憶體在),并為這些行建立非聚集索引,這樣可以使用索引的seek功能來僅輸出那些滿足SEEK()謂詞的行。
  • Table Spool 運算符是把表資料存放到tempdb中的臨時結構中,該操作符掃描輸入的資料表,把每行的副本放置在隐藏的Spool表中,此表叫做worktable,存儲在tempdb資料庫中,且隻在查詢的生命周期記憶體在
  • Window Spool 操作符和OVER() 視窗函數息息相關,因為隻有OVER()函數才會使用到Window Spool 操作符。

二,Lazy Spool調優

在查詢計劃中出現Spool操作符,意味着查詢語句需要存儲臨時資料集,以便在執行過程中重用這些資料。在查詢語句執行的生命周期内,SQL Server為了存儲資料,會在tempdb中建立臨時表,然後把臨時資料集存儲到臨時表中,這個操作會給硬碟帶來額外的IO開銷。tempdb的使用最終會使查詢語句的開銷增加,并且常常導緻查詢性能不佳。

Lazy Spool之是以被成為懶假脫機,這是因為它僅在收到請求時才會把資料加載到臨時結構中,并且在加載資料時不會停止資料流。雖然Lazy Spool是一個非阻塞的操作符,但是當有大量的資料需要處理時,它的開銷會非常大。

當查詢計劃中出現多個Lazy Spool操作符時,這種情況可能會導緻非常嚴重的性能問題,例如:

SET STATISTICS IO ON
GO

SELECT [InvoiceID], [OrderID]
FROM [Sales].[Invoices] o
WHERE [TotalDryItems] = (SELECT AVG([TotalDryItems])
FROM [Sales].[Invoices] o1
WHERE o.[CustomerID] = o1.[CustomerID]
GROUP BY [CustomerID])
GO      

當你執行上述查詢之後,從SSMS中你可以看到如下的執行計劃圖形:

性能調優6:Spool 假脫機調優

如果你仔細檢視這個執行計劃,你會發現三個不同的Lazy Spool操作符,每一個操作符的開銷都是0%,但是,當你切換到Message Tab,你會看到IO的統計資訊,Lazy Spool操作符把大量把大量的資料存儲到Worktable中,也就是說,消耗了14.4萬的邏輯讀操作,把資料寫入到tempdb中。

(17214 rows affected)

Table 'Invoices'. Scan count 9, logical reads 11994, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Table 'Worktable'. Scan count 24, logical reads 143680, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

要對這個查詢進行性能調優,必須保證執行計劃不會把大量的資料加載到tempdb中,通常修複Lazy Spool性能低下的做法是建立新的索引。

在上述的示例查詢中,有兩個where子句,一個where子句是關于CustomerID的,另一個是關于TotalDryItems,是以,我們在表[Sales].[Invoices]上建立一個關于這兩列的索引,首先檢視這兩列的選擇性(即唯一值的數量),把高選擇性的列作為索引的第一列:

SELECT COUNT(DISTINCT [TotalDryItems]) AS [CountTotalDryItems]
       ,COUNT (DISTINCT [CustomerID]) As [CountCustomerID]
FROM [Sales].[Invoices]
GO      

列CustomerID唯一值的數量是663,列TotalDryItems唯一值的數量是6,由于CustomerID列的選擇性高,是以,把CustomerID作為索引的第一列:

CREATE NONCLUSTERED INDEX [IX_FirstTry]
ON [Sales].[Invoices]
([CustomerID] ASC, [TotalDryItems] ASC)
INCLUDE ([InvoiceID], [OrderID])
GO      

重新執行示例查詢語句,得到一下的執行計劃圖示:

性能調優6:Spool 假脫機調優

從執行計劃圖示中可以清楚地看出,對表[Sales].[Invoices]執行Index Scan操作,由于索引IX_FirstTry比聚集索引掃面要窄的多,是以這是一個比Clustered Index Scan快很多的操作。最重要的是,執行計劃中沒有Lazy Spool操作符,不需要把資料寫入tempdb,然後再從tempdb中讀取,這大大提升了查詢語句的執行性能。

檢視Message Tab,檢查IO統計:

Table 'Invoices'. Scan count 2, logical reads 396, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

從統計資訊中,可以看出,沒有worktable,是以查詢不需要把資料寫入tempdb,再從tempdb中讀取資料。另外,由于使用了索引 IX_FirstTry,對Invoices執行的邏輯讀的次數,從11994減少到396,是以,查詢語句的IO性能成倍提升。 

三,Eager Spool 應用場景 

在更新語句中,如果執行計劃使用聚集索引來查找資料行,那麼執行計劃不會使用Eager Spool操作符;如果執行計劃使用非聚集索引來查找資料,那麼執行計劃就會使用Eager Spool操作符來與資料源隔離。

例如,下面的語句使用非聚集索引(IX_Price)來查找和讀取資料:

UPDATE Inventory SET Price = Price * 1.1
FROM Inventory WITH (INDEX = IX_Price)
WHERE Price < 100.00      
性能調優6:Spool 假脫機調優

從上面的執行計劃中,可以看到,從非聚集索引IX_Price讀取資料之後,SQL Server使用Table Spool(Eager Spool)阻塞性操作符,它讀取Index Seek操作符輸入的所有資料之後,把資料寫入到tempdb資料庫中。這樣,Update語句不會從非聚集索引IX_Price讀取任何資料,取而代之,Update語句使用Eager Spool操作符來執行資料的讀取操作。

From above execution plans, we see that after reading the data from non-clustered in IX_Price SQL Server uses Table Spool(Eager Spool) blocking operator. It reads all data and then moves to next operator. In our example, Eager Spool will read all data from IX_Price then move to tempdb and hence later on UPDATE doesn’t read non-clustered index IX_Price anymore and instead all reads are performed using Eager Spool operator.

 如果SQL Server不适用Eager Spool操作符,SQL Server 需要直接從非聚集索引IX_Price中讀取資料,定位到目标資料行,然後逐行更新資料。問題是在這種情景下,非聚集索引中行的位置可能被重置,導緻資料被多次更新,使用Eager Spool可以避免這個問題。

參考文檔:

Performance Tuning Made Easy – Optimizing Lazy Spool

SQL Server Eager Spool operator – Part2

Operator of the Week – Spools, Eager Spool

SQLSERVER中的假脫機

作者

:悅光陰

出處

:http://www.cnblogs.com/ljhdo/

本文版權歸作者和部落格園所有,歡迎轉載,但未經作者同意,必須保留此段聲明,且在文章頁面醒目位置顯示原文連接配接,否則保留追究法律責任的權利。

繼續閱讀