2010-08-26 57 views
4

我有一个很大的查询,其中一个简单的子查询优化从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行。在win情况下(使用变量而不是子查询),估计的行只有大约125k。这是怎么回事?为什么有更多的行涉及如果该子查询在那里? Pets.Name列肯定有唯一的数据(但据我所知,没有唯一的约束)。

请注意,将谓词移至WHERE子句不会影响查询,正如我所期望的那样,因为它是INNER JOIN。

深入了解!

+0

使用变量可能导致不同的计划。它通常会导致更糟糕的计划,因为变量的值在编译时并不知道。也许你在这个场合很幸运。也许专注于实际计划中的估计行数与实际行数,以查看是否有任何可能的统计问题。当您查看缓慢运行的实际执行计划时,您是否可以看到多次执行的子查询? – 2010-08-26 17:29:33

+0

@Martin Smith - 我可以看到正在执行的查询作为索引查找,并将其作为其他输入放入带有RID查找的嵌套循环中。这是非常低的成本 - 但令人惊讶的是,进一步的一些操作,它将它推到哈希匹配与非常大表中的集群索引扫描,这是一个巨大的成本。在查询的好版本中 - 这些操作都不存在。 – womp 2010-08-26 17:52:43

回答

4

据我的经验,更复杂的查询中获取,能力稍逊的SQL优化器是创建麻利计划。在这里,你有16个连接,有些或大部分是外连接,你至少有一个子查询......足够的索引,基数,视图,外部应用,谁知道还有甚么,甚至没有人,甚至微软工程师*可以找出能够统一定期生成最优方案的程序。

你所描述的,我经历过很多次 - 在一个混乱的查询中改变一个简单的事情,并且所有事情都快了一个数量级(或者,牙齿变得更慢)。我没有办法确定什么时候复杂过于复杂,这比什么都更有感觉。我的一般经验法则是,如果它看起来太长或太复杂,请简化你可以在哪里 - 例如你预先选择的单一嵌套值,或者突破部分查询,而不是总是快速运行,结果很小设置并首先运行它并将结果存储在临时表中。

(*请注意,这是温和的sarcsam)

4

作为替代方案,我想你可以消除子查询有:

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

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

+0

这个想法已经超越了我的想法,但查询是不相关的,所以我一直认为它会被独立评估。我会尝试创建约束,看看会发生什么。 – womp 2010-08-27 15:58:15

0

原因就像您指出的那样,并且从我的经验来看,通常甚至最简单的不相关子查询通常都是由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) 

然后联接重写可能无法如此轻松地工作。