SQL Server-聚焦LEFT JOIN...IS NULL AND NOT EXISTS性能分析(十七)


前言

本節我們來分析LEFT JOIN和NOT EXISTS,簡短的內容,深入的理解,Always to review the basics。

LEFT JOIN...IS NULL和NOT EXISTS分析

之前我們已經分析過IN查詢在處理空值時是基於三值邏輯,只要子查詢中存在空值此時則沒有任何數據返回,而LEFT JOIN和NOT EXISTS無論子查詢中有無空值上處理都是一樣的,當然比較重要的是利用LEFT JOIN...IS NULL來檢查NULL。基於二者返回的結果集是一樣的,下面我們開始直接用前面節所創建表來進行測試。在BigTable和SmallerTable上首先未創建索引

USE TSQL2012
GO

DBCC FREEPROCCACHE
DBCC DROPCLEANBUFFERS

SET STATISTICS IO ON
SET STATISTICS TIME ON

SELECT BigTable.ID, SomeColumn
  FROM BigTable LEFT OUTER JOIN SmallerTable ON BigTable.SomeColumn = SmallerTable.LookupColumn
  WHERE LookupColumn IS NULL

SELECT ID, SomeColumn FROM BigTable
WHERE NOT EXISTS (SELECT LookupColumn FROM SmallerTable WHERE SmallerTable.LookupColumn = BigTable.SomeColumn)

 

二者執行CPU Time和elapsed Time如下

我們看到上述查詢計划未創建索引之前二者在開銷上接近一致,而LEFT JOIN....IS NULL則首先進行哈希匹配中的右外部聯接,然后就是過濾,換句話說是LEFT JOIN....IS NULL會直接完全JOIN,然后再對重復數據進行過濾,而NOT EXISTS則是直接利用哈希匹配中的右半聯接,關於半聯接我們在前面也已經說過,此時若有重復數據直接只取一個。所以LEFT JOIN....IS NULL和NOT EXISTS二者對於重復數據一個通過兩部操作完成先完全JOIN后進行過濾,而另外一個則是直接通過右半聯接過濾。所以對於此二者最大的不同在於:當使用LEFT JOIN.....IS NULL時,SQL還沒有那么聰明,僅僅只檢查一次,因此它需要通過完全JOIN和過濾來完成,而NOT EXISTS則是在JOIN時就進行過濾。

在看二者執行CPU TIME和elapsed TIME時間,沒有太大的差異。接下來我們再來創建索引看看。

CREATE INDEX idx_BigTable_SomeColumn
ON BigTable (SomeColumn)
 
CREATE INDEX idx_SmallerTable_LookupColumn
ON SmallerTable (LookupColumn)

看看二者的查詢執行計划

 

此時我們通過看到上述查詢執行計划,我們能夠清楚的看到LEFT JOIN....IS NULL還是完全JOIN然后在過濾,只是創建了索引之后性能改善了一點而已,但是不同於LEFT JOIN...IS NULL的NOT EXISTS的計划執行情況不同於未創建索引,此時首先利用了流聚合然后哈希匹配中的右半聯接變成了合並聯接中的右半聯接,我們一個個來看,這個Stream Aggregate(流聚合)是什么鬼,對於此流聚合我是不了解的,不能裝懂,我們接下來具體講講流聚合,至於為什么每當查詢計划出現一個新的名詞都要去詳細了解下的原因,相信看過我SQL Server本系列的童鞋知道,每一節的內容都非常短,不會出現閱讀疲勞,而且是精講,我重頭系統學習SQL Server是為了對SQL Server中所有涉及到對性能調優有關的地方以及一些基礎知識都會去過一遍,以便后續再出現性能調優不至於束手無策。好了,回到話題,我們看看Stream Aggregate。

Stream Aggregate

msdn上有關概念如下:Stream Aggregate運算符按一列或多列對行分組,然后計算查詢返回的一個或多個聚合表達式。此運算符的輸出可供查詢中的后續運算符引用和/或返回到客戶端。Stream Aggregate 運算符要求輸入在組中按列進行排序。如果由於前面的 Sort 運算符或已排序的索引查找或掃描導致數據尚未排序,優化器將在此運算符前面使用一個 Sort 運算符。在 SHOWPLAN_ALL 語句或 SQL Server Management Studio 的圖形執行計划中,GROUP BY 謂詞中的列會列在 Argument 列中,而聚合表達式列在 Defined Values 列中。 

通過上述定義僅僅只是知道Stream Aggregate是用對行或者列進行聚合,至於什么時候在查詢計划中出現流聚合,什么時候利用流聚合來提高查詢性能都是不得而知,我們接下來一起探討下。上述着重在於【分組】然后進行【聚合】計算,基於這點我們來看看使用Stream Aggregate的三種場景。

(1)聚合匯總

USE TSQL2012
GO

SELECT COUNT(custid) AS cutid, SUM(empid) AS empid
FROM Sales.Orders

(2)先分組,再聚合匯總

USE TSQL2012
GO

SELECT custid, COUNT(custid) AS countCustId
FROM Sales.Orders
GROUP BY custid

(3)DISTINCT匯總

USE TSQL2012
GO

SELECT DISTINCT custid
FROM Sales.Orders

上述查詢使用通過DISTINCT,實際上是對cutid進行了分組。以上是用到了Stream Aggregate的場景,當然聚合還有另外一種就是哈希匹配聚合,后續會再進行補充。我們再來理解Stream Aggregate定義,我們將定義概括為對輸入進行排序后,接下來進行分組然后再進行聚合計算。在上述(2)和(3)中都是進行了分組,但是沒有排序,實際上內部已經默認實現了排序,我們看下在(3)中表中custid數據,如下

當進行DISTINCT之后

但是在(3)中沒有進行聚合,為什么會進行流聚合呢?實際上在流聚合中存在狀態變量,狀態變量具體個數根據聚合個數而定,此狀態變量用來設置結果集,當進行分組后對應的數據進行保存,此時對應的狀態變量為0,當匹配到對應數據時此時狀態變量加1,所以上述(3)中可以說隱式進行了聚合計算,只是每條數據對應的狀態變量為0而已,到了這里就不難解釋,只進行了排序,分組而沒有進行聚合計算的原因。關於Stream Aggregate都知道的一個例子則是我們在利用SqlDataReader記性讀取數據時,可以說是讀取流記錄,如果我們需要匯總結果集時,此時每當Read時,其內部的狀態變量都會加1最終返回匯總和到客戶端。在這里我們只是簡單講講Stream Aggregate,后續會一並講講Hash Aggregate。我們繼續回到LEFT JOIN....IS NULL和NOT EXISTS話題,當我們創建索引之后此時LEFT JOIN....IS ISNULL執行時間是NOT EXISITS的兩倍多。到此,關於LEFT JOIN...IS NULL和NOT EXISTS就此結束,我們同樣下個基本結論。

LEFT JOIN...IS NULL和NOT EXISTS性能分析結論:當我們需要找到子查詢中不匹配的行並且列為可空時,此時用NOT EXISTS,當需要找到子查詢中不匹配的行,此時列不為空時可以用NOT EXISTS或者NOT IN。

由於LEFT JOIN..IS NULL對於不匹配的行不會立即進行返回而先需要完全JOIN后過濾,尤其是當有多個條件時,LEFT JOIN...IS NULL可能會更加影響查詢性能。

總結

本節我們學習了LEFT JOIN..IS NULL和NOT EXISTS的性能分析,下節我們進入這幾節內容的綜合篇,綜合比較NOT IN VS NOT EXISTS VS LEFT JOIN...IS NULL終極篇。簡短的內容,深入的理解,我們下節再會。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM