SQL服务器连接选择比使用本地表的连接选择慢

时间:2013-06-07 00:09:06

标签: sql sql-server-2008 query-optimization

我有两个非常大的查询,有多个选择。所有选择都是 JOINED (左,内和右连接)。例如:

Declare @p2 Table (
...
)

Insert into @p2
    Select * from D 
          join E on D.id = E.id
          left join F on E.id2 = F.id
          ....

    Select * from(
       (Select * from A 
          join B on A.id = B.id
          join C on B.id2 = C.id
          ....
       ) as PART1
    right join  
       @p2 as PART2
    on PART1.ID = PART2.ID)

此代码工作正常,需要大约20秒才能执行。我运行执行计划,它显示57%的时间用于插入。为了优化此查询,我创建了一个查询,如:

    Select * from(
       (Select * from A 
          join B on A.id = B.id
          join C on B.id2 = C.id
          ....
       ) as PART1
    right join  
       (Select * from D 
          join E on D.id = E.id
          left join F on E.id2 = F.id
          ....) as PART2
    on PART1.ID = PART2.ID)

我预计这会显着提高速度,但结果却慢了4倍。你知道为什么吗?

2 个答案:

答案 0 :(得分:1)

你的两个问题不一样。第一种是将结果的一部分存储在表变量中。第二个是将它包含在查询中。

执行计划几乎必然不同。要了解这些差异,您需要查看查询计划。

以下是可能导致性能与预期不同的一些原因:

  • 从数据库返回行的开销可能比插入表中的开销要大得多。
  • SQL查询在编译时可能有更多关于表变量的信息,因为在编译第二部分时已经创建了表变量。
  • 即使所有统计信息都已更新,组合查询也可能只选择较差的执行计划。也就是说,一起执行“P2”查询可能是最佳的,但它可能会选择另一个计划。

答案 1 :(得分:1)

你没有提供很多信息,所以我的回答非常一般。

当SQL Server优化查询时,它会尝试查找最佳的可执行执行计划。但是,对于复杂查询,可能的计划数量如此之高,以至于无法进行详尽的搜索。

如果您查看一个简单的两个表连接,例如SELECT * FROM A JOIN B ON A.id = B.id;,您有6个选项来创建计划:

  • A LOOP JOIN B
  • A MERGE JOIN B
  • HASH JOIN B
  • B LOOP JOIN A
  • B MERGE JOIN A
  • B HASH JOIN A

这甚至不考虑索引搜索或扫描等不同的表访问操作符。如果表上有多个索引,则会比这更复杂。 (有关不同联接类型和联接运算符的详细信息,请查看我的联接系列:http://sqlity.net/en/1146/a-join-a-day-introduction/

现在,如果A,B,C之间有三个表连接,则有六种方法可以对表进行排序,两种连接操作符有3种选项,每种方法共有54种方法来创建一个计划,只需查看连接运算符和表订购。这几乎增加了十倍。对于六个表,有近20万个选项,八个表几乎有1亿个。同样,这只是表顺序和连接运算符,并没有考虑潜在的索引和表访问方法。

SQL Server不是试图完成所有可能需要花费更长时间才能执行查询的计划,而是根据每个表数据的统计信息支持的启发式和规则来查看少数几个。这些启发式通常非常好,通常SQL Server会找到一个非常接近最佳可能的计划。但是,有一些情况是这个过程下降了。如果统计数据已过期,则特别容易发生这种情况。

因此,首先要看的是这些统计数据。如果这没有帮助,请尝试添加筛选的统计信息和筛选的索引。如果您在企业中,则可以创建索引视图以预先计算连接的某些内容。 (除非您明确说明,否则标准版不会使用索引视图。)

之后,您可以查看提供联接提示,以帮助SQL Server提出一个好的计划。

如果所有这些都无济于事,请将查询拆分为较小的部分,并将中间结果存储在临时对象中。在@table_variables没有统计信息的情况下,使用#temp_tables通常会更好,所以它们的使用会对下一步造成伤害。