【问题标题】:Why is this (non-correlated) subquery causing such problems?为什么这个(不相关的)子查询会导致这样的问题?
【发布时间】:2010-08-26 17:19:21
【问题描述】:

我有一个大型查询,其中一个简单的子查询优化将其从 8 分钟缩短到 20 秒。我不确定我是否理解为什么优化会产生如此巨大的影响。

本质上,这是问题所在:

SELECT  (bunch of stuff)
FROM
  a LEFT OUTER JOIN b ON a.ID = b.a
  LEFT OUTER JOIN c ON b.ID = c.b
  ...
  ...
      INNER JOIN veryLargeTable 
      ON a.ID = veryLargeTable.a 
         AND veryLargeTable.PetID = 
             (SELECT id from Pets WHERE Pets.Name = 'Something')    /* BAD! */
  ...
  ...

总共有 16 个连接表。如果我将 veryLargeTable 连接的第二个谓词替换为包含 petID 的预填充变量(而不是使用子查询),则整个查询会显着加快速度

AND veryLargeTable.PetID = @petID   /* Awesome! */


显然,(SELECT id from Pets WHERE Name = 'Something') 正在为每一行执行。有两点我不太明白:

  1. 据我所知,这是一个不相关的子查询。 Pets 表根本不是外部查询的一部分。不相关的子查询不是独立评估(并因此优化)吗?为什么这里不是这种情况?

  2. 执行计划有很大不同。在失败的情况下(上图),整个子树处理估计有 950k 行。在获胜的情况下(使用变量而不是子查询),估计只有大约 125k 行。这是怎么回事?如果存在该子查询,为什么还要涉及这么多行? Pets.Name 列肯定有唯一数据(但据我所知没有唯一约束)。

请注意,将谓词移动到 WHERE 子句在任何一种情况下都不会影响查询,正如我所期望的那样,因为它是一个 INNER JOIN。

感谢您的见解!

【问题讨论】:

  • 使用变量会导致不同的计划。尽管变量的值在编译时是未知的,但它通常会导致更糟糕的计划。也许你只是在这个场合走运了。也许关注实际计划中的估计行数与实际行数,看看是否存在任何可能的统计问题。当您查看慢速运行的实际执行计划时,您真的可以看到子查询被执行了多次吗?
  • @Martin Smith - 我可以看到查询正在作为索引搜索执行,并且它被放入嵌套循环中,其中 RID 查找作为另一个输入。这是非常低的成本 - 但令人惊讶的是,进一步的一些操作会将其推入哈希匹配中,并在 veryLargeTable 上进行聚集索引扫描,这是一个巨大的成本。在查询的好版本中 - 这些操作都不存在。

标签: sql sql-server sql-server-2005 tsql subquery


【解决方案1】:

根据我的经验,查询越复杂,SQL 优化器创建灵巧计划的能力就越差。在这里你有 16 个连接,一些或大部分是外连接,你至少有一个子查询...折腾足够多的索引、基数、视图、外部应用,谁知道还有什么,没有人知道,甚至微软也不知道工程师*,可以找出将统一并定期生成最佳计划的例程。

你所描述的,我已经经历过无数次了——在一个混乱的查询中改变一件简单的事情,一切都会快一个数量级(或者,咬牙切齿,更慢)。我没有办法确定什么时候复杂太复杂,这更像是一种感觉。我的一般经验法则是,如果它看起来太长或太复杂,请尽可能简化 - 例如您预先选择的单个嵌套值,或者拆分查询的一部分而不是总是运行使用小结果集快速运行,首先运行它并将结果存储在临时表中。

( * 请注意这是轻微的讽刺)

【讨论】:

  • 十多年后,我建立了大量的方法和策略来弄清楚为什么大查询运行缓慢[SARG、基数、倾斜数据、参数嗅探等] ... 很容易需要一个小时或更长时间才能弄清楚对庞大数据库进行复杂查询的情况。有时一个明显的索引会加快速度,有时你只需要删除几年的“谁在乎”数据……而且我的旧经验法则通常仍然适用。
【解决方案2】:

作为替代方案,我认为您可以通过以下方式消除子查询:

...
INNER JOIN veryLargeTable vLT
    ON a.ID = vLT.a 
INNER JOIN Pets p
    ON vLT.PetID = p.id
        and p.Name = 'Something'
...

【讨论】:

    【解决方案3】:

    我个人认为,如果 Pets.Name 上没有索引,结果也就不足为奇了。如果您在 Pets.Name 上创建唯一索引,您可能会看到更好的结果。从服务器的角度来看,如果没有索引,子查询可能会返回多行或 NULL。也许优化器可以做得更好;它经常需要帮助。

    【讨论】:

    • 这个想法从我脑海中掠过,但查询是不相关的,所以我认为它会被独立评估。我将尝试创建约束,看看会发生什么。
    【解决方案4】:

    原因正如您所指出的,根据我的经验,即使是最简单的不相关子查询也经常被 SQL Server 的查询优化器重新计算。

    例如,您可以查看以下查询的执行计划,并看到重新计算了不相关的子查询。

    SELECT ID
    FROM #table1
    WHERE ID in (SELECT ID from #table1)
    UNION ALL
    SELECT ID
    FROM #table1
    WHERE ID in (SELECT ID from #table1)
    

    这与属性(在本例中为“ID”)上是否有聚集索引有关。正如有人指出的那样,您可以重写此查询以使用联接而不是子查询。然而,在许多情况下,如果子查询返回一个聚合标量,例如

    where ID = (select MAX(ID) from #table1)
    

    那么连接重写可能不会那么容易。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-11-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多