在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等价形式,将会减小不少麻烦。