Not In的主要問題是由于對Null值的處理問題所導(dǎo)致,那么對Null值的處理究竟為什么會(huì)導(dǎo)致性能問題?讓我們來看圖5的示例。圖5中,我們使用了Adventurework示例數(shù)據(jù)庫,并為了演示目的將SalesOrderDetail表的ProductId的定義由Not Null改為Null,此時(shí)我們進(jìn)行一個(gè)簡單的Not In查詢。如圖5所示。
圖5.Not In的執(zhí)行計(jì)劃
在圖5中,我們看到一個(gè)Row Count Spool操作符,該操作符用于確認(rèn)ProductId列中是否有Null值(過程是對比總行數(shù)和非Null行數(shù),不想等則為有Null值,雖然我們知道該列中沒有Null值,但由于列定義是允許Null的,因此SQL Server必須進(jìn)行額外的確認(rèn)),而該操作符占用了接近一半的查詢成本。因此我們對比Not Exists,如圖6所示。
圖6.Not In Vs Not Exists
由圖6可以看出,Not In的執(zhí)行成本幾乎是Not Exists的3倍,僅僅是由于SQL Server需要確認(rèn)允許Null列中是否存在Null。根據(jù)圖3中Not In的等價(jià)形式,我們完全可以將Not In轉(zhuǎn)換為等價(jià)的Not Exist形式,如圖7所示。
圖7.Not In轉(zhuǎn)換為Not Exists
我們來對比圖7和其等價(jià)Not In查詢的成本,如圖8所示。
圖8.成本上完全等價(jià)
因此我們可以看到Not In需要額外的步驟處理Null值,上述情況是僅僅在SalesOrderDetail表中的ProductId列定義為允許Null,如果我們將SalesOrderHeader的SalesOrderID列也定義為允許Null時(shí),會(huì)發(fā)現(xiàn)SQL Server還需要額外的成本確認(rèn)該列上是否有Null值。如圖9所示。
圖9.SQL Server通過加入Left Anti Semi Join操作符解決列允許Null的問題
此時(shí)Not In對應(yīng)的等價(jià)Not Exist形式變?yōu)槿绱a清單1所示。
SELECT *FROM Sales.SalesOrderHeader aWHERE NOT EXISTS ( SELECT *FROM Sales.SalesOrderDetail bWHERE a.SalesOrderID = b.ProductID )AND NOT EXISTS ( ( SELECT *FROM Sales.SalesOrderDetail bWHERE b.ProductID IS NULL) )AND NOT EXISTS ( SELECT 1FROM ( SELECT *FROM Sales.SalesOrderHeader) AS cWHERE c.SalesOrderID IS NULL )
代碼清單1.當(dāng)連接列兩列定義都允許Null時(shí),Not In等價(jià)的Not Exists形式
此時(shí)我們簡單對比Not In和Not Exists的IO情況,如圖10所示。
圖10.Not In吃掉很高的IO