在SQL Server中,子查詢能夠分爲相關子查詢和無關子查詢,對於無關子查詢來講,Not In子句比較常見,但Not In潛在會帶來下面兩種問題:數據庫
下面咱們來看一下爲何儘可能不使用Not In子句。app
在SQL Server中,Null值並非一個值,而是表示特定含義,其所表示的含義是「Unknow」,能夠理解爲未定義或者未知,所以任何與Null值進行比對的二元操做符結果必定爲Null,包括Null值自己。而在SQL Server中,Null值的含義轉換爲Bool類型的結果爲False。讓咱們來看一個簡單的例子,如圖1所示。性能
圖1.Null值與任何值進行對比結果都爲Nullspa
SQL Server提供了「IS」操做符與Null值作對比,用於衡量某個值是否爲Null。3d
那麼Not In 的問題在哪呢,如圖2所示。code
圖2.Not In產生不許確的值blog
在圖2中,條件3不屬於Not In後面列表的任意一個,該查詢卻不返回任何值,與預期的結果不一樣,那麼具體緣由就是Not In子句對於Null值的處理,在SQL Server中,圖2中所示的Not In子句其實能夠等價轉換爲如圖3所示的查詢。ip
圖3.對於Not In子句來講,能夠進行等價轉換get
在圖3中能夠看到Not In能夠轉換爲條件對於每一個值進行不等比對,並用邏輯與鏈接起來,而前面提到過Null值與任意其餘值作比較時,結果永遠爲Null,在Where條件中也就是False,所以3<>null就會致使不返回任何行,致使Not In子句產生的結果在乎料以外。it
所以,Not In子句若是來自於某個表或者列表很長,其中大量值中即便存在一個Null值,也會致使最終結果不會返回任何數據。
解決辦法就是不使用Not In,而使用Not Exists做爲替代。Exists的操做符不會返回Null,只會根據子查詢中的每一行決定返回True或者False,當遇到Null值時,只會返回False,而不會由某個Null值致使整個子查詢表達式爲Null。對於圖2中所示的查詢,咱們能夠改寫爲子查詢,如圖4所示。
圖4.Not Exists能夠正確返回結果
前面咱們能夠看出,Not In的主要問題是因爲對Null值的處理問題所致使,那麼對Null值的處理究竟爲何會致使性能問題?讓咱們來看圖5的示例。圖5中,咱們使用了Adventurework示例數據庫,併爲了演示目的將SalesOrderDetail表的ProductId的定義由Not Null改成Null,此時咱們進行一個簡單的Not In查詢。如圖5所示。
圖5.Not In的執行計劃
在圖5中,咱們看到一個Row Count Spool操做符,該操做符用於確認ProductId列中是否有Null值(過程是對比總行數和非Null行數,不想等則爲有Null值,雖然咱們知道該列中沒有Null值,但因爲列定義是容許Null的,所以SQL Server必須進行額外的確認),而該操做符佔用了接近一半的查詢成本。所以咱們對比Not Exists,如圖6所示。
圖6.Not In Vs Not Exists
由圖6能夠看出,Not In的執行成本幾乎是Not Exists的3倍,僅僅是因爲SQL Server須要確認容許Null列中是否存在Null。根據圖3中Not In的等價形式,咱們徹底能夠將Not In轉換爲等價的Not Exist形式,如圖7所示。
圖7.Not In轉換爲Not Exists
咱們來對比圖7和其等價Not In查詢的成本,如圖8所示。
圖8.成本上徹底等價
所以咱們能夠看到Not In須要額外的步驟處理Null值,上述狀況是僅僅在SalesOrderDetail表中的ProductId列定義爲容許Null,若是咱們將SalesOrderHeader的SalesOrderID列也定義爲容許Null時,會發現SQL Server還須要額外的成本確認該列上是否有Null值。如圖9所示。
圖9.SQL Server經過加入Left Anti Semi Join操做符解決列容許Null的問題
此時Not In對應的等價Not Exist形式變爲如代碼清單1所示。
SELECT *
FROM Sales.SalesOrderHeader a
WHERE NOT EXISTS ( SELECT *
FROM Sales.SalesOrderDetail b
WHERE a.SalesOrderID = b.ProductID )
AND NOT EXISTS ( ( SELECT *
FROM Sales.SalesOrderDetail b
WHERE b.ProductID IS NULL
) )
AND NOT EXISTS ( SELECT 1
FROM ( SELECT *
FROM Sales.SalesOrderHeader
) AS c
WHERE c.SalesOrderID IS NULL )
代碼清單1.當鏈接列兩列定義都容許Null時,Not In等價的Not Exists形式
此時咱們簡單對比Not In和Not Exists的IO狀況,如圖10所示。
圖10.Not In吃掉很高的IO
本文闡述了Not In 的實現原理以及所帶來的數據不一致和性能問題,在寫查詢時,儘可能避免使用Not In,而轉換爲本文提供的Not Exists等價形式,將會減小不少麻煩。