前言
上一節我們分析了INNER JOIN和IN,對於不同場景其性能是不一樣的,本節我們接着分析NOT EXISTS和NOT IN,簡短的內容,深入的理解,Always to review the basics。
初步探討NOT EXISTS和NOT IN
NOT EXISTS和NOT IN有很大的不同,尤其是對NULL的處理,為何這樣說,當子查詢中有NULL時,此時NOT IN不會返回任何行,下面我們來看下簡單的示例。
USE TSQL2012 GO WITH table1 AS ( SELECT 1 AS value UNION ALL SELECT NULL AS nullcol1 ), table2 AS ( SELECT 2 AS value UNION ALL SELECT NULL AS nullcol2 )
首先我們來通過NOT EXISTS來進行查詢
SELECT * FROM table1 AS a
WHERE NOT EXISTS(SELECT * FROM table2 AS b WHERE a.value = b.value)
接下來我們再來進行NOT IN查詢
SELECT * FROM table1 AS a
WHERE value NOT IN(SELECT * FROM table2)

為何會出現不一樣的結果呢,我們來分析下EXISTS和IN,EXISTS使用的是兩值謂詞邏輯,也就說說EXISTS總是返回TRUE或者FALSE,絕對不會返回UNKNOWN,而IN使用的三值謂詞邏輯即返回的是TRUE或者FALSE或者UNKNOWN。當我們進行NOT EXISTS查詢時,此時用1和NULL兩行數據,此時1與table2中的值進行等值比較,此時沒有相同的返回FALSE,接着NOT EXISTS則返回TRUE,所以此時返回1,同理返回NULL,當利用上述NOT IN進行查詢時,我們可以將上述進行如下等價
SELECT * FROM table1 AS a
WHERE value NOT IN(SELECT * FROM table2)
等價於
WHERE ( value != (SELECT value FROM table2 WHERE value = 2) AND value != (SELECT value FROM table2 WHERE value = NULL) )
當value = 1時,此時則有TRUE AND UNKNOWN結果還是UNKNOWN,同理當value = NULL時也是返回UNKNOWN,所以最終結果都未匹配上沒有任何數據返回。
進一步探討NOT EXISTS和NOT IN
接下來我們來進行NOT EXISTS和NOT IN的性能分析,接下來我們通過三種情況來進行分析。
(1)未建立索引情況比較NOT EXISTS和NOT IN
我們還是利用上一節的BigTable和SmallerTable來進行測試。
USE TSQL2012
GO
SELECT ID, SomeColumn FROM BigTable
WHERE SomeColumn NOT IN (SELECT LookupColumn FROM SmallerTable)
SELECT ID, SomeColumn FROM BigTable
WHERE NOT EXISTS (SELECT LookupColumn FROM SmallerTable WHERE SmallerTable.LookupColumn = BigTable.SomeColumn)

此時發現NOT EXISTS和NOT IN開銷一致,解下來我們創建索引看看。
(2)創建索引比較NOT EXISTS和NOT IN
CREATE INDEX idx_BigTable_SomeColumn ON BigTable (SomeColumn)
CREATE INDEX idx_SmallerTable_LookupColumn ON SmallerTable (LookupColumn)
繼續進行上述查詢

創建了索引結果還是一致和上一節我們討論的INNER JOIN和IN的情況有點不太一樣,即使是創建唯一非聚集索引二者性能開銷還是一致。到這里我們是不是可以下結論說二者性能一致呢,我們繼續往下看,不知道大家發現了沒有我們在上一節開始時對查詢列的約束是不為空的,那要是為空結果又會是怎樣的呢,我們看看。
(3)將查詢列修改為可空
我們將SomeTable表和SmallerTable表中的SomeCloumn和LookupColumn修改為可空
USE TSQL2012
GO
ALTER TABLE BigTable
ALTER COLUMN SomeColumn UNIQUEIDENTIFIER NULL
ALTER TABLE SmallerTable
ALTER COLUMN LookupColumn UNIQUEIDENTIFIER NULL

查詢計划顯示結果大大出乎我們意料,為什么將列修改為可空的,此時NOT IN的性能開銷接近是NOT EXISTS的33倍,猜測的話數據量越大這個差距應該是越來越明顯。不知道為何如此,反正查詢計划是如此,欺騙不了我們。在SQL Server 2012基礎教程后續中無意中看到這樣一句話:對EXISTS來說它會過濾掉NULL值。是不是當列定義為NULL時,IN不會過濾掉NULL,而EXISTS即使定義為NULL也會被自然過濾呢,不得而知。通過上述我們明確知道,有時候將列定義為空會減少我們的不必要的判斷,但是在NOT EXISTS和NOT IN比較中,此時通過定義為NULL將會得到巨大的差異,至此,我們可以得出如下結論。
NOT EXISTS和NOT IN性能分析結論:當將查詢列定義為NULL時,NOT EXISTS比NOT IN性能要好很多,當定義為非NULL時此時二者查詢開銷一樣。當然如沒有特殊情況,還是建議將查詢列定義為非NULL,這樣既可以保證查詢性能,也可以保證在使用過程中NOT IN的安全性,減少不必要的性能開銷。
總結
本節我們詳細探討了NOT EXISTS和NOT IN的性能情況,下一節我們開始探討EXIST和IN的性能分析,簡短的內容,深入的理解,我們下節再會。
