前言
本節我們來綜合比較NOT IN VS NOT EXISTS VS LEFT JOIN...IS NULL的性能,簡短的內容,深入的理解,Always to review the basics。
NOT IN、NOT EXISTS、LEFT JOIN...IS NULL性能分析
我們首先創建測試表
USE TSQL2012 GO CREATE SCHEMA [compare] CREATE TABLE [compare].t_left ( id INT NOT NULL PRIMARY KEY, value INT NOT NULL, stuffing VARCHAR(200) NOT NULL ) CREATE TABLE [compare].t_right ( id INT NOT NULL PRIMARY KEY, value INT NOT NULL, stuffing VARCHAR(200) NOT NULL ) GO
接着我們在兩個表中的列value上創建索引
USE TSQL2012
GO
CREATE INDEX idx_left_value ON [compare].t_left (value)
CREATE INDEX idx_right_value ON [compare].t_right (value)
我們在t_left和t_right表中插入如下測試數據
USE TSQL2012 GO BEGIN TRANSACTION DECLARE @cnt INT SET @cnt = 1 WHILE @cnt <= 100000 BEGIN INSERT INTO [compare].t_left VALUES ( @cnt, @cnt % 10000, LEFT('Left ' + CAST(@cnt AS VARCHAR) + ' ' + REPLICATE('*', 200), 200) ) SET @cnt = @cnt + 1 END; WITH rows AS ( SELECT 1 AS row UNION ALL SELECT row + 1 FROM rows WHERE row < 10 ) INSERT INTO [compare].t_right SELECT (id - 1) * 10 + row + 1, value + 1, LEFT('Right ' + CAST(id AS VARCHAR) + ' ' + REPLICATE('*', 200), 200) FROM [compare].t_left CROSS JOIN rows COMMIT
我們稍微解釋下上述插入的測試數據:
(1)t_left表中插入10萬條數據,其中包含1萬條重復數據。
(2)t_right表中插入100萬條數據,其中包含1萬條重復數據。
(3)t_left表中插入10條t_right表中沒有的數據。
接下來我們一個個來看看其查詢執行計划。
NOT IN性能分析
USE TSQL2012
GO
SET STATISTICS IO ON
SET STATISTICS TIME ON
SELECT l.id, l.value
FROM [compare].t_left l
WHERE l.value NOT IN
(
SELECT value
FROM [compare].t_right r
)
我們重點看看上述圖做了標記的兩個重要的地方,最后返回結果集時使用了Merge Anti Semi Join也就是說是上述Merge Join和Right Anti Semi Join的結合,可以說這是一種非常高效的方式,事先通過索引來排序然會獲取兩個表的結果集。數據庫通過Merge Join來迭代兩個表的結果集從小值到大值,當然也是通過指針指向二者結果集的當前值然后接着指向下一個值。而Anti Semi Join主要是干什么的呢?前面我們講過它是半聯接,此時數據庫引擎只要匹配到t_right表中的值就跳過所有t_left和t_right表其他也同樣匹配的同一個值,為什么會跳過呢? 因為此時Stream Aggregate起到了決定性作用(【關於Stream Aggregate前面簡單了解了下,感覺理解的還是不夠透,寫這篇文章時才算是灰常了解了,后續會專門寫寫Stream Aggregate和Hash Aggregate】)我們知道Stream Aggregate首先需要排序,然后進行分組接着就是聚合,因為我們建立了索引所以就有了排序,接着執行Stream Aggregate進行分組,通過查看Stream Aggregate如下具體信息知道。因為對t_right表中的值進行了分組,所以當進行合並右半聯接時,只取組中第一個,其余的自然而然就進行跳過,所以這種方式非常高效,通過索引來進行排序,再通過Stream Aggregate進行分組,最后執行Merge Join(Right Anti Semi Join)。最后我們看到查詢僅僅只耗費了0.315秒。
NOT EXISTS性能分析
我們運行如下查詢
USE TSQL2012 GO SET STATISTICS IO ON SET STATISTICS TIME ON SELECT l.id, l.value FROM [compare].t_left l WHERE NOT EXISTS ( SELECT NULL FROM [compare].t_right r WHERE r.value = l.value )
關於其查詢耗費時間就不再給出了,其實NOT EXISTS和NOT查詢計划和查詢時間都是一樣的,並沒有任何區別,我們之前在單獨討論NOT EXISTS和NOT IN時就已經明確說過,二者在查詢列不為NULL的前提下,二者的查詢開銷是一樣的,而將查詢列設置為可NULL時,NOT EXISTS的性能遠高於NOT IN,這里我們就不過多的討論了,不明白的童鞋可以看看前面關於二者比較的文章。
LEFT JOIN....IS NULL性能分析
USE TSQL2012 GO SET STATISTICS IO ON SET STATISTICS TIME ON SELECT l.id, l.value FROM [compare].t_left l LEFT JOIN [compare].t_right r ON r.value = l.value WHERE r.value IS NULL
到這里我們知道很顯然結果集肯定是一樣的,但是查詢計划和上述NOT EXISTS、NOT IN有很大的差異,LEFT JOIN...IS NULL首先是使用LEFT JOIN返回所有數據,其中包括重復的,然后再進行過濾,為什么會先進行LEFT JOIN然后再進行Filter呢?因為SQL Server根本無法很智能的識別LEFT JOIN上緊跟着的IS NULL,所以需要兩步操作來完成。此時我們需要過濾100萬條數據,這是一個非常耗時的工作,所以此時利用非常高效的Hash Match並且是並行的,但是過濾這些值還是要花費很長時間。整個時間花費了0.989秒,其查詢耗費時間是NOT EXISTS或者NOT IN的3倍。所以到這里,關於此三者我們可以定下如下這樣一個結論。
NOT IN VS NOT EXISTS VS LEFT JOIN..IS NULL結論:當查詢缺省值時利用NOT EXISTS和NOT IN是最佳方式,但是前提是二者查詢列都不能為NULL,否則使用NOT EXISTS。而LEFT JOIN...IS NULL因其總是不會跳過已經匹配過的值而是利用先返回所有結果集然后過濾的方式,其低效性可想而知。
總結
本節我們比較了NOT EXISTS和NOT IN和LEFT JOIN..IS NULL的性能,最終得出了三者性能分析結論,下一節我們已經確定是最后一篇終極篇比較EXISTS VS IN VS JOIN的性能,簡短的內容,深入的理解,我們下節再會。