【问题标题】:Why does IF (query) take over an hour, when query takes less than a second?当查询不到一秒时,为什么 IF(查询)需要一个多小时?
【发布时间】:2013-03-16 02:39:06
【问题描述】:

我有以下 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),则需要一个多小时。 为什么?

现在,显然,执行计划有所不同 - 我可以在 Enterprise Manager 中看到 - 但同样,为什么

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


编辑

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


编辑 2

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

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

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

【问题讨论】:

  • 有趣的问题。你能提供给我们执行计划吗?
  • 强制系统计算行数,而不是在确定any行存在后立即返回永远不要悲观——即使它确实改善了当前的性能问题,也只是一个意外,而不是解决根本问题。
  • 运行查询需要多长时间:select case when 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) ) then 1 else 0 end? (通过使用case when exists,这应该使它能够短路而不是进行完整计数。)

标签: sql sql-server


【解决方案1】:

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

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

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

我建议以下步骤:

  1. 通过查看索引和统计数据来修正计划
  2. 如果这没有帮助,请将查询更改为 IF (SELECT COUNT(*) FROM ...) > 0,这将给出原始计划,因为优化器没有行目标。

【讨论】:

  • 这听起来很有说服力和合乎逻辑。但是,我尝试从 IF EXISTS(...) 转换为 IF (SELECT COUNT(*) FROM ...) > 0 并没有什么区别。也许优化器“聪明”地认为 COUNT 的结果仅用作存在测试:-)
  • 将内部查询更改为SELECT TOP 1 1 FROM ... 确实使它运行非常缓慢。有点走错了方向,虽然:-)
  • 很好,你测试了它。我认为它不会“获得” COUNT 的存在测试,所以我不知道为什么计划不会改变。您可以将计划发布为图像吗?也许您确实可以使用您在问题中发布的局部变量技巧或尝试其他重写。
  • 等等——当你建议我改变EXISTS时,你是指外部EXISTS还是内部?我试着改变外面的,但不是里面的……我现在试试。
  • 嗯,这行得通。向你致敬! (我使用的是 SQL Server 2008 R2)。
【解决方案2】:

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

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

【讨论】:

  • IF EXISTS( SELECT 1 FROM ... 更改为IF EXISTS( SELECT TOP 1 KeyField FROM ... 只会使内部查询变慢,而不是加快IF
  • 我刚刚对这个问题做了一些分析。通常使用半连接存在,即在找到第一条记录后查询将终止。在我的猜测中,当在存在中使用存在时,似乎没有应用半连接。当您运行内部查询时,您只有一个存在语句,因此应用了半联接。但是当您使用 if exists 时,只有外部存在将使用半联接,因此内部查询需要完全运行。
【解决方案3】:

这可能是因为优化器可以弄清楚如何将您的查询变成更有效的查询,但 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 子句中测试空值来过滤掉所有连接的行,该子句在连接完成后执行

【讨论】:

  • Left join ... where null 在 MySQL 中的运行速度肯定比 not exists 快,但它在 SQLServer 中确实如此吗? (例如,我的理解是在 Oracle 中not exists 更快。)
  • 谢谢,但我已经试过了 - 不幸的是它没有任何区别:-(
  • @MarkBannister 很可能。我的猜测是 IF 导致 opyimizer 被绕过。没有解释输出,我们无法知道。我承认我对 SQL Server 几乎没有经验 - 如果你说,我相信。
  • @Bohemian:在 SQLServer 中,我不知道(这就是我问的原因)。我有一个快速的谷歌,这个问题:stackoverflow.com/questions/6777910 和这篇博文:sqlinthewild.co.za/index.php/2010/03/23/… 似乎表明如果有合适的索引可用,not exists 在 SQLServer 中会更快。
猜你喜欢
  • 2012-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-27
相关资源
最近更新 更多