Sql 为什么IF(query)需要一个多小时,而查询只需要不到一秒钟?

Sql 为什么IF(query)需要一个多小时,而查询只需要不到一秒钟?,sql,sql-server,Sql,Sql Server,我有以下SQL: IF EXISTS ( SELECT 1 FROM SomeTable T1 WHERE SomeField = 1 AND SomeOtherField = 1 AND NOT EXISTS(SELECT 1 FROM SomeOtherTable T2 WHERE T2.KeyField = T1.KeyField) ) RAISERROR ('Blech.', 16, 1)

我有以下SQL:

 IF EXISTS
 (
    SELECT
        1
    FROM
        SomeTable T1
    WHERE
        SomeField = 1
    AND SomeOtherField = 1
    AND NOT EXISTS(SELECT 1 FROM SomeOtherTable T2 WHERE T2.KeyField = T1.KeyField)
)
    RAISERROR ('Blech.', 16, 1)
SomeTable
表大约有200000行,而
SomeOtherTable
表也差不多

如果我执行内部SQL(选择
命令
),它会在亚秒的时间内执行,不会返回任何行。但是,如果我执行整个脚本(
if…RAISERROR
),则需要一个多小时为什么?

现在,很明显,执行计划是不同的——我可以在Enterprise Manager中看到这一点——但是,为什么呢

我可能会做一些类似于
选择@num=COUNT(*)的事情,其中
。。。然后
如果@num>0出现错误
但是。。。我认为这有点漏掉了重点。如果您知道某个bug的存在,您只能围绕它进行编码(在我看来它确实像个bug)


编辑

我应该提到,我已经尝试按照@Bohemian的回答将查询重新组合到外部联接中,但这对执行时间没有影响


编辑2

我已附上内部
SELECT
语句的查询计划:

。。。以及整个
IF…RAISERROR
块的查询计划:


很明显,它们显示了真实的表/字段名,但除此之外,查询与上面所示完全相同。

这可能是因为优化器可以找出如何将查询转换为更高效的查询,但不知何故,IF阻止了这一点。只有解释才能告诉你为什么查询要花这么长时间,但我可以告诉你如何使整个过程更高效。。。为了避免使用关联子查询(效率极低,在主表中为“n”行运行“n”个子查询),请使用联接

试试这个:

IF EXISTS (
  SELECT 1
  FROM SomeTable T1
  LEFT JOIN SomeOtherTable T2 ON T2.KeyField = T1.KeyField
  WHERE SomeField = 1
  AND SomeOtherField = 1
  AND T2.KeyField IS NULL
) RAISERROR ('Blech.', 16, 1)

这里的“诀窍”是使用s LEFT JOIN并通过测试WHERE子句中的null来过滤掉所有连接的行,该子句在连接完成后执行。

IF
不会神奇地关闭优化或破坏计划。优化器刚刚注意到
存在
最多只需要一行(如
前1行
)。这称为“行目标”,通常在进行分页时发生。还有
存在
中,
不在
中等等

我的猜测是:如果您将
TOP 1
写入原始查询,您将得到相同(错误)的计划

优化器试图在这里变得聪明,只使用更便宜的操作生成第一行。不幸的是,它错误地估计了基数。它猜测查询将产生很多行,但实际上它不会产生任何行。如果它正确估计,你只能得到一个更有效的计划,否则它根本不会进行转换

我建议采取以下步骤:

  • 通过查看索引和统计数据来确定计划
  • 如果这没有帮助,请将查询更改为
    if(选择COUNT(*)FROM…)>0
    ,这将给出原始计划,因为优化器没有行目标

  • 请尝试
    选择前1个关键字段
    。在我看来,使用主键会更快


    注意:我将此作为答案发布,因为我无法发表评论

    左连接。。。其中null在MySQL中的运行速度肯定比不存在的要快,但它在SQLServer中运行得快吗?(例如,我的理解是,在Oracle中,
    not exists
    的速度更快。)谢谢,但我已经尝试过这一点——不幸的是,这没有什么区别:-(@MarkBannister很可能是。我猜是IF导致opyimizer被绕过。如果没有解释输出,我们无法知道。我承认我对SQL Server几乎没有经验-如果你说的话,我相信。@Bohemian:在SQL Server中,我不知道(这就是我问的原因)。我用谷歌快速搜索了一下,这个问题:这篇博文:似乎表明,如果有合适的索引可用,
    不存在,
    在SQLServer中会更快。有趣的问题。你能给我们提供执行计划吗?强制系统计算行数,而不是让它在确定后立即返回如果有任何行存在,那只能是一种悲观情绪——即使它确实改善了当前的性能问题,但这将是一种意外,而不是解决根本问题。运行查询需要多长时间:
    select case when exists(从SomeTable T1中选择1,其中SomeField=1,SomeOtherField=1,并且不存在)(从其他表T2中选择1,其中T2.KeyField=T1.KeyField)),然后选择1,其他0结束
    ?(如果存在
    ,则应使用
    ,这将使其短路,而不是进行完整计数。)@MarkBannister:Ages:-)但请参见usr的答案-他似乎已经确定了“悲观化”的原因(我今天从Damien_不信者那里学到的新词)。这听起来很有说服力和逻辑性。但是,我尝试了从
    IF EXISTS(…)
    转换到
    IF(SELECT COUNT(*)from…)>0
    ,结果没有什么不同。也许优化器是“聪明的”关于计数结果仅用作存在性测试的事实:-)将内部查询更改为
    selecttop11 FROM…
    确实会使它运行得非常慢。虽然有点走错方向:-)很好,您测试了它。我认为它不会“得到”使用COUNT进行存在性测试,因此我不知道为什么该计划不会更改。你能将该计划作为图像发布吗?也许你确实可以使用你在问题中发布的局部变量技巧或尝试其他重写。等等-当你建议我更改
    EXISTS
    时,你是指外部
    EXISTS
    还是内部
    吗?我尝试了ch更改外部的,而不是内部的…我现在就试试。好吧,这很有效。谢谢你!(我使用的是SQL Server 2008 R2)。更改
    如果存在(从…
    中选择1)到
    如果存在(从…
    中选择顶部1键域)只会使内部查询速度慢得多,而不是速度慢得多