为什么一个查询非常慢,但在类似的表上相同的查询在眨眼间运行

时间:2011-08-12 07:55:26

标签: sql-server query-performance

我有这个查询......运行得非常慢(差不多一分钟):

select distinct main.PrimeId 
from PRIME main 
join   
( 
select distinct p.PrimeId   from PRIME p   
left  outer join ATTRGROUP a 
on p.PrimeId = a.PrimeId   or p.PrimeId = a.RelatedPrimeId    
where a.PrimeId is not null and a.RelatedPrimeId is not null  
) mem  
on main.PrimeId = mem.PrimeId

PRIME表有18k行,并且在PrimeId上有PK。

ATTRGROUP表有24k行,并且在PrimeId,col2上有复合PK,然后是RelatedPrimeId,然后是cols 4-7。在RelatedPrimeId上还有一个单独的索引。

查询最终返回8.5k行 - PRIME表上的PrimeId的不同值,与ATTRGROUP表中的PrimeId或RelatedPrimeId匹配

我有相同的查询,使用ATTRADDRESS而不是ATTRGROUP。 ATTRADDRESS具有与ATTRGROUP相同的密钥和索引结构。它只有11k行,不过可以肯定,但是在这种情况下,查询运行大约一秒钟,然后返回11k行。

所以我的问题是:

尽管结构相同,查询如何在一个表上比另一个表慢得多。

到目前为止,我已经在SQL 2005上尝试了这一点,并且(使用相同的数据库,已升级)SQL 2008 R2。我们两个人独立地获得了相同的结果,将相同的备份恢复到两台不同的计算机。

其他细节:

  • 括号内的位在不到一秒的时间内运行,即使在慢查询
  • 中也是如此
  • 执行计划中可能存在一些线索,我不明白。这是它的一部分,可疑的320,000,000行操作:

enter image description here enter image description here

但是,该表上的实际行数略多于24k,而不是320M!

如果我在括号内部重构查询部分,那么它使用UNION而不是OR,因此:

select distinct main.PrimeId 
from PRIME main 
join   
( 
select distinct p.PrimeId   from PRIME p   
left  outer join ATTRGROUP a 
on p.PrimeId = a.PrimeId
where a.PrimeId is not null and a.RelatedPrimeId is not null  
UNION
select distinct p.PrimeId   from PRIME p   
left  outer join ATTRGROUP a 
on p.PrimeId = a.RelatedPrimeId    
where a.PrimeId is not null and a.RelatedPrimeId is not null  
) mem  
on main.PrimeId = mem.PrimeId

...然后慢速查询需要一秒钟。

我非常感谢对此有任何见解!如果您需要更多信息,请告诉我,我会更新问题。谢谢!

顺便说一下,我意识到在这个例子中有一个冗余连接。这不容易被删除,因为在生产中,整个事物是动态生成的,括号中的位有许多不同的形式。


修改

我在ATTRGROUP上重建了索引,没有太大的区别。

编辑2

如果我使用临时表,那么:

select distinct p.PrimeId into #temp
from PRIME p   
left  outer join ATTRGROUP a 
on p.PrimeId = a.PrimeId   or p.PrimeId = a.RelatedPrimeId    
where a.PrimeId is not null and a.RelatedPrimeId is not null  

select distinct main.PrimeId 
from Prime main join   
#temp mem  
on main.PrimeId = mem.PrimeId

...然后,即使在原始的OUTER JOIN中使用OR,它也会在不到一秒的时间内运行。我讨厌像这样的临时表,因为它总是让人觉得是失败的承认,所以它不是我将要使用的重构,但我觉得有趣的是它会产生这样的差异。

编辑3

更新统计数据也没有区别。

感谢您提出的所有建议。

4 个答案:

答案 0 :(得分:6)

根据我的经验,最好在JOIN子句中使用两个左连接而不是OR。 所以而不是:

    left  outer join ATTRGROUP a 
    on p.PrimeId = a.PrimeId   or p.PrimeId = a.RelatedPrimeId

我建议:

    left  outer join ATTRGROUP a 
    on p.PrimeId = a.PrimeId
    left  outer join ATTRGROUP a2
    on p.PrimeId = a2.RelatedPrimeId    

答案 1 :(得分:3)

我注意到主查询与子查询没有关联:

select distinct main.PrimeId 
from PRIME main 
join   
( 
select distinct p.PrimeId   from PRIME p   
left  outer join ATTRGROUP a 
on p.PrimeId = a.PrimeId
where *main.PrimeId = a.PrimeId*  
UNION
select distinct p.PrimeId   from PRIME p   
left  outer join ATTRGROUP a 
on p.PrimeId = a.RelatedPrimeId    
where *main.PrimeId = a.PrimeId*  
) mem  
on main.PrimeId = mem.PrimeId

在这种结构中,你也不需要使用'is not null'子句(你是否需要它,因为主键永远不会保存空值?)。

我被教导要避免OR构造(正如其他人已经建议的那样),但也要避免'is not null'或'in valuelist ' - 构造。这些主要可以用(NOT)EXISTS子句代替。

答案 2 :(得分:1)

这不是一个直接的答案,但是如果你有FK约束从ATTRGROUP.PrimeId和ATTRGROUP.RelatedPrimeId引用到main,那么你的查询相当于这个更简单的一个:

select PrimeId   from ATTRGROUP a 
union
select RelatedPrimeId from ATTRGROUP a 

答案 3 :(得分:0)

一个查询在一个表上的速度比另一个表慢得多的一个原因是该表上的统计信息已过期并且选择了错误的查询计划。

但是我支持重构,摆脱其他人建议的或者条款。