在IF EXISTS中包装查询会使它非常慢


16

我有以下查询:

select databasename 
from somedb.dbo.bigtable l where databasename ='someval' and source  <>'kt'
and not exists(select 1 from dbo.smalltable c where c.source=l.source)

上面的查询将在三秒钟内完成。

如果上面的查询返回任何值,我们希望存储过程为EXIT,因此我将其重写如下:

If Exists(
select databasename 
from somedb.dbo.bigtable l where databasename ='someval' and source  <>'kt'
and not exists(select 1 from dbo.smalltable c where c.source=l.source)
)
Begin
Raiserror('Source missing',16,1)
Return
End

但是,这需要10分钟。

我可以像下面这样重写上面的查询,它也可以在不到3秒的时间内完成:

  select databasename 
from somedb.dbo.bigtable l where databasename ='someval' and source  <>'kt'
and not exists(select 1 from dbo.smalltable c where c.source=l.source
if @@rowcount >0
Begin
Raiserror('Source missing',16,1)
Return
End

上面重写的问题在于,上面的查询是更大的存储过程的一部分,并且它返回多个结果集。在C#中,我们遍历每个结果集并进行一些处理。

上面的返回一个空结果集,因此,如果我采用这种方法,则必须更改C#并再次进行部署。

所以我的问题是

为什么只使用IF EXISTS更改计划就需要这么多时间?

以下是可能对您有帮助的详细信息,如果您需要任何详细信息,请告诉我:

  1. 创建表和统计脚本以获取与我相同的计划
  2. 慢执行计划
  3. 快速执行计划

    使用Brentozar的慢速计划粘贴计划通过Brentozar的慢速计划
    粘贴计划

注意:这两个查询都是相同的(使用参数),唯一的区别是EXISTS(尽管匿名时我可能会犯一些错误)。

表创建脚本如下:

http://pastebin.com/CgSHeqXc- 小表统计
http://pastebin.com/GUu9KfpS- 大表统计


有关此问题的讨论已移至此聊天室
保罗·怀特

Answers:


18

如已经由保罗·怀特:在他的博客里面的优化:行目标在深度EXISTS介绍一排目标,即喜欢NESTED LOOPSMERGE JOIN以上HASH MATCH

作为最后一个示例,请考虑逻辑半联接(例如EXISTS引入的子查询)具有整体主题:应对其进行优化以快速找到第一个匹配的行。

在您的查询中,这显然是由于引入了嵌套循环并消除了并行性,从而导致了较慢的计划。

因此,您可能需要找到一种无需使用查询中的即可重写NOT EXISTS查询的方法。

您可以通过使用来重写查询,LEFT OUTER JOIN并通过检查以下内容来检查smalltable中是否没有行:NULL

If EXISTS(
    SELECT databasename
    FROM somedb.dbo.bigtable l
    LEFT JOIN dbo.smalltable c ON c.source = l.source
    WHERE databasename = 'someval'
    AND source <> 'kt'
    AND c.source IS NULL
)

您可能也可以使用EXCEPT查询,具体取决于您需要像这样比较的字段数:

If EXISTS(
   SELECT source
   FROM somedb.dbo.bigtable l
   WHERE databasename = 'someval'
   AND source <> 'kt'

   EXCEPT

   SELECT source
   FROM dbo.smalltable
)

请注意,Aaron Bertrand的博客文章中提供了为什么他偏爱“不存在”的原因,您应该仔细阅读该原因,以了解其他方法是否效果更好,并要注意在使用NULL值时可能存在的正确性问题。

相关问答:IF EXISTS比嵌入式select语句花费的时间更长


0

您需要使用显式联接重写查询,并指定要使用的联接操作(循环,哈希或合并),如下所示。

If not exists(
    select databasename 
    from somedb.dbo.bigtable l
    inner hash join dbo.smalltable c 
        on c.source = l.source
where databasename ='someval' and source  <>'kt')
begin
    Raiserror('Source missing',16,1)
    Return
end

当使用EXISTS或NOT EXISTS时,SQL Server使用NESTED LOOP操作生成的查询计划假定它应该一个接一个地遍历集合中的所有行,以寻找满足条件的第一行。使用HASH JOIN可以加快速度。


比您测试的
更好

0

我碰到过同样的问题,通过避免使用“ EXISTS”以及通过使用“ COUNT()”函数和“ IF ... ELSE”语句,我确实设法解决了自己的问题。

对于您的示例,请尝试以下操作:

IF
(
    SELECT
        COUNT(l.databasename) + 1 AS databasename
    FROM somedb.dbo.bigtable AS l

    WHERE   l.databasename ='someval'
        AND l.[source]  <> 'kt'
        AND NOT EXISTS(SELECT 1 FROM dbo.smalltable AS c WHERE c.[source]=l.[source])
) > 1 --Acts like EXISTS
BEGIN
    RAISERROR('Source missing', 16, 1)
RETURN
END

我将“ +1”添加到计数中的原因是,以便我可以在IF条件中使用“> 1”,使用“> 0”或“ <> 0”将触发查询使用嵌套循环而不是HASH比赛。尚未调查到底为什么会发生,所以找出原因很有趣。

希望有帮助!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.