为什么IF(查询)需要一个多小时,查询时间不到一秒?

时间:2013-03-26 10:28:11

标签: 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)

SomeTable表有大约200,000行,SomeOtherTable表大致相同。

如果我执行内部SQL(SELECT),它会在亚秒内执行,不返回任何行。但是,如果我执行整个脚本(IF...RAISERROR),则需要一个多小时。的为什么吗

现在,显然,执行计划是不同的 - 我可以在企业管理器中看到 - 但同样,为什么

我可能会做SELECT @num = COUNT(*) WHERE ...然后IF @num > 0 RAISERROR之类的事情,但......我认为这有点遗漏了这一点。如果您知道它存在,您只能编写一个错误代码(对我来说它确实看起来像个错误)。


修改

我应该提一下,我已经尝试按照@ Bohemian的回答将查询重新连接到OUTER JOIN,但这对执行时间没有任何影响。


编辑2

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

Query Plan - inner SELECT statement

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

Query Plan - whole IF statement

显然这些显示了真实的表/字段名称,但除此之外,查询完全如上所示。

3 个答案:

答案 0 :(得分:6)

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

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

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

我建议采取以下步骤:

  1. 通过查看索引和统计信息来修复计划
  2. 如果这没有帮助,请将查询更改为IF (SELECT COUNT(*) FROM ...) > 0,这将提供原始计划,因为优化程序没有行目标。

答案 1 :(得分:2)

这可能是因为优化器可以弄清楚如何将查询转换为更有效的查询,但不知何故IF阻止了这一点。只有一个EXPLAIN会告诉你为什么查询花了这么长时间,但是我可以告诉你如何使整个事情更有效...而不是使用相关的子查询,这是非常低效的 - 你得到“n”子查询运行主表中的“n”行 - 使用JOIN。

试试这个:

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来过滤掉所有连接的行,该子句在连接之后执行

答案 2 :(得分:0)

请尝试SELECT TOP 1 KeyField。使用主键在我的猜测中会更快。

注意:我发布此答案是因为我无法发表评论。