“执行”;不在「;在SQL查询中

“执行”;不在「;在SQL查询中,sql,sql-server,performance,Sql,Sql Server,Performance,我对SQL查询分析非常陌生。最近,我在其中一个查询中偶然发现了一个性能问题,我想知道我的思考过程是否正确,以及为什么查询优化器在这种情况下工作。 我是om SQL Server 2012。 我有一个SQL查询,看起来像 SELECT * FROM T1 WHERE Id NOT IN (SELECT DISTINCT T1_Id from T2); 在我的测试服务器上运行大约需要30秒。 在试图理解什么花费了这么长时间的同时,我使用临时表重写了它,如下所示: SELECT DISTINCT

我对SQL查询分析非常陌生。最近,我在其中一个查询中偶然发现了一个性能问题,我想知道我的思考过程是否正确,以及为什么查询优化器在这种情况下工作。 我是om SQL Server 2012。 我有一个SQL查询,看起来像

SELECT * FROM T1 
WHERE Id NOT IN 
(SELECT DISTINCT T1_Id from T2);
在我的测试服务器上运行大约需要30秒。 在试图理解什么花费了这么长时间的同时,我使用临时表重写了它,如下所示:

SELECT DISTINCT T1_Id 
INTO #temp from T2;
SELECT * FROM T1 
WHERE Id NOT IN  
(SELECT T1_Id from #temp);
它比第一台快一百倍

有关表格的一些信息: T2大约有100万行,其中大约有1000个不同的T1_id值。T1大约有1000多行。最初,我在T2上只有一个聚集索引,而不是在T1_Id列上,所以T1_Id根本没有索引

查看执行计划,我发现对于第一个查询,索引扫描的数量与不同的T1_id值的数量一样多,因此基本上SQLServer在本例中执行大约1000次索引扫描。 这让我意识到,在T1_id上添加一个非聚集索引可能是一个好主意(无可否认,索引从一开始就应该存在),添加一个索引确实使原始查询运行得更快,因为现在它执行非聚集索引搜索

我想了解的是原始查询的查询优化器行为——它看起来合理吗?有没有办法让它以类似于我在这里发布的临时表变体的方式工作,而不是进行多次扫描?我只是误解了什么吗?
提前感谢您提供类似讨论的链接,因为我没有发现任何有用的内容

SQL Server优化器需要了解一些决策的if表的大小

使用子查询执行
NOT IN
时,这些估计可能不完全准确。当表实际实现时,计数将非常准确

我认为,如果有索引,第一个会更快

Table2(t1_id)

不是凭直觉,而是缓慢。这种构造通常运行得更快

where id in 
(select id from t1
except select t1_id from t2)

实际性能可能与估计值不同,但您的两个查询都不会超过此查询,这是事实上的标准方法:

SELECT T1.* FROM T1 
LEFT JOIN T2 ON T1.Id = T2.T1_Id
WHERE T2.T1_Id IS NULL
这使用了一个正确的联接,它将执行得非常好(假设外键列被索引),并且是左(外部)联接
WHERE
条件只选择
T1
中没有联接的行(当联接未命中时,右侧表的所有列都是
null

还要注意的是,不需要使用
DISTINCT
,因为对于错过的联接,
T1
只返回一行。

这只是一个猜测,但希望是一个有根据的猜测

DBMS可能得出结论,少量搜索大型表要比大量搜索小型表快。这就是为什么您在
T2
上搜索了约1000次,而不是在
T1
上搜索了约1000000次

当您在
T2.T1_Id
上添加索引时,将~1000次表扫描(或如果表是聚集的,则为完全聚集索引扫描)转变为~1000次索引搜索,这使速度大大加快,正如您已经指出的

我不确定它为什么不尝试散列连接(或添加索引后的合并连接)——也许它的统计数据陈旧,并且严重高估了不同值的数量

还有一件事:
T2.T1\u Id
引用
T1.Id
时是否有外键?我知道Oracle可以使用FKs来提高成本估算的准确性(在这种情况下,它可以推断
T2.T1_Id
的基数不能大于
T1.Id
)。如果MS SQL Server执行类似的操作,并且FK缺失(或存在),这可能会导致MS SQL Server认为存在比实际更多的不同值


(顺便说一句,如果你公布了实际的查询计划和数据库结构,这会有所帮助。)

我同意你关于索引的看法,我在OP中提到我最终添加了索引。至于桌子的大小,请你澄清一下“物化桌子”是什么意思?这是我作为DB用户所能做的吗?“物化”只是一种花哨的说法,表示创建了一个表或中间表。顺便说一句,“in”子查询中不需要“distinct”。谢谢你,我明白了。因此,在这种情况下,如果不重写查询,根本无法更改优化器的行为?我怀疑这是一个非常常见的情况,不在,不存在和类似的操作?有一个显式连接的方法。这个问题通常是优化器选择嵌套循环联接,提示可以避免这个问题。令人惊讶的是(对我来说),我还没有在“in”子查询中遇到这个问题,所以我不知道提示是否会直接作用于查询(重写左外部联接很容易)。非常感谢您提供的信息,@Gordon Linoff!非常感谢,非常有用的提示!与不存在/不存在相比,此模式实际上非常昂贵。Aaron,很棒的文章,我在这里发布之前读过。你喜欢用什么方式写这个不存在/不存在?在这种情况下,是否应该使用中间临时表来避免多个索引查找?@AlexSh不知道,我们必须实际查看实际的执行计划,以了解为什么它的使用速度较慢。您知道您不需要distinct(这可能会增加不必要的排序),对吗?@AaronBertrand这种模式执行得很好。与不在中的
不同,它也很简单且可扩展。因此,..NOT EXISTS的工作方式与第一个查询相同-即,索引扫描/搜索的执行次数与T1_id的唯一值的执行次数相同,这很好,但它仅在知道NOT in列不可为空时才起作用。“不存在”不会落入那个陷阱,所以为什么在某些情况下它是安全的时候不使用“不存在”,而不是在它总是安全的时候使用“不存在”?至于这件事发生了什么,请告诉我