11

我有以下 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 (the SELECT),它会在亚秒时间内执行,不会返回任何行。但是,如果我执行整个脚本 ( IF...RAISERROR),则需要一个多小时。为什么?

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

我可能会做类似SELECT @num = COUNT(*) WHERE......然后IF @num > 0 RAISERROR但是......我认为这有点忽略了这一点。如果您知道它存在,您只能围绕一个错误(而且它在我看来确实像一个错误)进行编码。


编辑

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


编辑 2

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

查询计划 - 内部 SELECT 语句

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

查询计划 - 整个 IF 语句

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

4

3 回答 3

6

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

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

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

我建议以下步骤:

  1. 通过查看索引和统计数据来确定计划
  2. 如果这没有帮助,请更改IF (SELECT COUNT(*) FROM ...) > 0将给出原始计划的查询,因为优化器没有行目标。
于 2013-03-26T11:16:03.537 回答
2

这可能是因为优化器可以弄清楚如何将您的查询变成更有效的查询,但是 IF 以某种方式阻止了这种情况。只有一个解释会告诉你为什么查询需要这么长时间,但我可以告诉你如何让整个事情更高效......而不是使用相关子查询,这是非常低效的 - 你会运行“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 子句中测试是否为空来过滤掉所有连接的行,该子句在连接完成后执行。

于 2013-03-26T10:48:16.003 回答
0

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

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

于 2013-03-26T11:33:26.513 回答