前言
之前系列中在查詢計划中一直出現Stream Aggregate,當時也只是做了基本了解,對於查詢計划中出現的操作,我們都需要去詳細研究下,只有這樣才能對查詢計划執行的每一步操作都了如指掌,所以才有了本文的出現,簡短的內容,深入的理解,Always to review the basics。
Stream Aggregate
Stream Aggregate通過單列或者多列來對行進行分組並且對指定的查詢來計算聚合表達式。最常見的聚合類型如SUM、COUNT、SUM、AVG、MIN、MAX,當我們執行這些聚合函數時在查詢計划中就會出現Stream Aggregate,Stream Aggregate是非常快的,因為它需要在輸入時通過在GROUP BY中指定的列進行排序。如果聚合中的數據沒有進行排序此時會通過Sort進行預排序或者使用索引查找或者索引掃描來提前預排序數據。之前我們討論過出現Stream Aggregate有三種方式分別為:聚合函數聚合,分組聚合,DISTINCT聚合,實際上只有兩種,DISTINCT內部就用到了分組,這里我們將Stream Aggregate分為兩種類型,一種是標量聚合,另外一種則是分組聚合。我們舉一個標量聚合的例子,也就是返回單值聚合。
標值聚合
USE TSQL2012 GO SELECT COUNT(*) FROM Sales.Orders
下面我們再來分組聚合的例子
USE TSQL2012
GO
SELECT custid
FROM Sales.Orders
GROUP BY custid
上述就是Stream Aggregate兩種類型的例子,關於標量聚合比較簡單直接利用聚合函數就行,下面我們主要詳細講解這兩種類型中的分組聚合。
分組聚合
我們來結合SQL Server 2012基礎教程來看一個簡單的例子
USE TSQL2012
GO
SELECT custid, COUNT(shipcity) AS [shipcity_count]
FROM Sales.Orders
GROUP BY custid
上述查詢計划比較簡單我們來解釋下,首先通過默認主鍵創建的聚集索引來讀取表中行數據,接着通過GROUP BY上指定的列custid來進行排序,我們看到其排序操作具體信息就知道,如下。接着遍歷所有custid,所有行被讀取,開始一行行讀取並計算其聚合表達式的值。重復處理直到完成為止。
對於通過流聚合對custid進行分組的示意圖大概如下:
上述由於未對custid創建索引導致所以會通過Sort來進行排序,毫無疑問導致查詢緩慢,這里我們對custid創建非聚集索引再來看看情況
CREATE NONCLUSTERED INDEX idx_nc_custid ON Sales.Orders(custid)
此時查詢將會充分利用索引,它會通過使用索引排序來進行聚合計算,所以就不會再利用Sort來排序導致性能低下,通過上述我們知道,在進行Stream Aggregate之前事實上在指定的分組列上創建索引來預先排序會提高查詢性能,而不需要再去利用Sort進行排序而耗費不必要的時間。上述我們已經說過在進行排序要么在GROUP BY上指定的列通過創建索引查找或者索引排序,如果GROUP BY中的列沒有創建索引此時利用Sort來進行顯示排序,如下顯示指定ORDER BY custid來排序和沒有指定的話結果依然都是使用Sort來排序,此時Stream Aggregate,其實這種說法不太准確,因為在SQL Server中有兩種聚合方式,一種是Stream Aggregate,另外一種則是Hash Match Aggregate。
USE TSQL2012
GO
SELECT custid, COUNT(shipcity) AS [shipcity_count]
FROM Sales.Orders
GROUP BY custid
ORDER BY custid
自從SQL Server 7之后就出現了Stream Aggregate和Hash Aggregate兩種聚合方式,也就是說上述我們稍作修改查詢計划就變成了Hash Aggregate的形式。
USE TSQL2012 GO DBCC RULEOFF('GbAggToStrm'); GO SELECT custid, COUNT(shipcity) AS [shipcity_count] FROM Sales.Orders GROUP BY custid OPTION(RECOMPILE) GO DBCC RULEON('GbAggToStrm');
上述GbAggToStrm是什么鬼,其實如果查詢計划中走的Stream Aggregate操作的話,也就說它走的是GbAggToStrm規則(GROUP BY Aggregate To Stream ),但是這里我們關閉了查詢計划本該走的Stream Aggregate操作即GbAggToStrm規則,所以此時它將只能走Hash Aggregate。所以到這里說明在排序時即使指定了ORDER BY操作有可能是多余的,但是如果我們不指定的話,要是我們希望返回的結果集是排序的,此時要是走的Hash Aggregate,結果返回的結果集將是無序的,導致我們得不到想要的結果集,所以還是希望在排序時指定ORDER BY操作,這樣能夠避免不必要的情況發生。
DISTINCT在Hash Match Aggregate和Stream Aggregate和DISTINCT Sort中的使用
當查詢中用到了DISTINCT關鍵字時,此時查詢計划有可能走Stream Aggregate,也有可能走的是Hash Match Aggregate。所以在這里我們分析下什么時候會用Hash Match Aggregate,什么時候又會用到Stream Aggregate。說到底DISTINCT關鍵字時用來去重的,在SQL Server中利用DISTINCT關鍵字來去重其查詢計划走的方式分為兩種,一種是在哈希表中建立唯一值,另外一種則是將行進行排序分配到組中然后只返回組中的一個值即可。所以在SQL Server中使用Hash Match Aggregate來實現哈希表,使用Stream Aggregate或者DISTINCT Sort來對數據進行排序去重。
使用DISTINCT關鍵字走DISTINCT Sort
當我們如下直接利用DISTINCT來查詢時就是利用的DISTINCT Sort來排序去重。
USE TSQL2012 GO select DISTINCT custid FROM Sales.Orders
雖然很明確走的Sort,但是這是經過SQL查詢引擎優化過后才有的,最原始的情況是先進行Sort接着進行Stream Aggregate,下面我們關閉Sort的規則看看。
USE TSQL2012 GO DBCC RULEOFF('GbAggToSort') SELECT DISTINCT custid FROM Sales.Orders OPTION(RECOMPILE)
DBCC RULEON('GbAggToSort')
使用DISTINCT關鍵字走Hash Match Aggregate
當未在列SomeColumn創建索引時我們進行如下查詢
USE TSQL2012
GO
SELECT DISTINCT SomeColumn
FROM dbo.BigTable
接下來我們在列上創建索引
CREATE NONCLUSTERED INDEX idx_noncls_somecolumn ON dbo.BigTable(SomeColumn)
在創建索引時此時查詢計划走的卻是Stream Aggregate,也就是說當利用DISTINCT關鍵字查詢時且列已經進行了排序,此時查詢計划走Stream Aggregate。那什么時候用Hash Match Aggregate呢,上述對列未創建索引時走的是Hash Match Aggregate因為數據量比較大此時還利用了並行計算,換句話說當對列未創建索引時且數據量非常大同時分組比較少時,查詢計划更加更傾向於走Hash Match Aggregate,輸入大量的數據通過Hash Match Aggregate結合並行計算效率也非常高,當然分組較少更好,此時不會太占用哈希表。接下來我們限制查詢結果集的條數。
USE TSQL2012 GO SELECT DISTINCT TOP 10 SomeColumn FROM dbo.BigTable
此時查詢計划不再是Hash Match Aggregate代替的是Hash Match(Flow Distinct)我們看下msdn關於Flow Distinct的解釋:Flow Distinct邏輯運算符用於通過掃描輸入來刪除重復項。雖然Distinct 運算符在生成任何輸入前消耗所有的輸入,但FlowDistinct 運算符在從輸入獲得行時返回每行(除非該行是一個重復項,若是這樣則刪除該行)
也就是說DISTINCT直接就過濾了重復行,而Flow Distict則獲得每行時並返回每一行,這就是Flow Distinct,它的出現依賴於在查詢計划中估計唯一值的數量,當我們將TOP的數量設置為接近100萬或者比100萬還少一點時此時走的是Hash Match Aggregate。到此我們關於Hash Match Aggregate和Stream Aggregate的分析算是結束了,我們下個基本結論:
Hash Match Aggregate和Stream Aggregate分析結論:
(1)查詢中有DISTINCT關鍵字時:當在查詢列上創建索引時即列進行了排序時此時走Stream Aggregate,當數據量非常大時且未創建索引時此時一般走的是Hash Match Aggregate並結合並行計算,其余情況則是走的Distinct Sort。
(2)查詢中沒有DISTINCT關鍵字時,對於標量聚合和分組聚合走的是Stream Aggregate。
總結
好了本節關於Hash Match Aggregate和Stream Aggregate的介紹就到此為止,基本算是了解,太復雜的也沒去過多探討,這是DBA的事情了,下一節我們穿插講講關於計算列持久化系列文章,簡短的內容,深入的理解,我們下節再會。