Sql 为什么这个(不相关的)子查询会导致这样的问题?

Sql 为什么这个(不相关的)子查询会导致这样的问题?,sql,sql-server,sql-server-2005,tsql,subquery,Sql,Sql Server,Sql Server 2005,Tsql,Subquery,我有一个大型查询,其中一个简单的子查询优化将它从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

我有一个大型查询,其中一个简单的子查询优化将它从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个联接表。如果我将VeryTargetable联接的第二个谓词替换为包含petID的预填充变量,而不是使用子查询,则整个查询的速度会显著加快:

显然,从Pets中选择id,其中Name='Something'正在为每一行执行。有两件事我没有完全理解:

据我所知,这是一个不相关的子查询。Pets表根本不是外部查询的一部分。非相关子查询不是独立计算并因此优化的吗?为什么这里不是这样

执行计划截然不同。在上面的失败案例中,整个子树处理大约950k行。在win案例中,使用变量而不是子查询,估计只有大约125k行。发生什么事?如果子查询存在,为什么会涉及这么多行?Pets.Name列肯定有唯一的数据,但据我所知没有唯一的约束

请注意,将谓词移动到WHERE子句不会像我预期的那样影响任何一种情况下的查询,因为它是一个内部联接


感谢您的真知灼见

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

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

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

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


*请注意,这是一个温和的问题

我个人认为,如果没有关于Pets.Name的索引,结果并不令人惊讶。如果你在Pets.Name上创建一个唯一的索引,你可能会看到更好的结果。如果从服务器的角度看没有索引,子查询可能返回多行或NULL。也许乐观主义者可以做得更好;它通常需要帮助。

原因正如您所指出的,并且根据我的经验,即使是最简单的非相关子查询,也常常由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)

那么,连接重写可能就不那么容易了。

使用变量可能会导致不同的计划。它通常会导致更糟糕的计划,尽管在编译时变量的值是未知的。也许你这次运气不错。可能会关注实际计划中的估计行数与实际行数,以查看是否存在任何可能的统计问题。当你查看慢运行的实际执行计划时,你真的能看到子查询被执行了不止一次吗?@Martin Smith-我能看到查询作为索引查找执行,它被放入一个嵌套循环中,RID查找作为另一个输入。这是一个非常低的成本——但令人惊讶的是,再进行一些操作,它就会将其推到一个哈希匹配中,并在VeryTargetable上进行聚集索引扫描,这是一个巨大的成本。在查询的好版本中,这些操作都不存在。我曾想过这个想法,但查询是不相关的,所以我认为它将被独立评估。我将尝试创建约束,看看会发生什么。十多年后,我建立了大量的方法和策略,以找出大型查询运行缓慢的原因[SARG、基数、扭曲数据、参数嗅探等]。。。对于ginormous数据库上的复杂查询,很容易需要一个小时或更长的时间才能弄清楚发生了什么。有时一个明显的索引会加快速度,有时你只需要删除几年谁在乎的数据。。而且经常 我过去的经验法则仍然适用。
where ID = (select MAX(ID) from #table1)