为什么外部连接比单独的查询更慢

时间:2015-09-14 14:09:42

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

我的查询基本上是这样的:

Select *
From UserSearches us
left outer join Quotes q on q.UserSearchId = us.Id and q.QuoteNumber is not null
left outer join ContainerDetails cd on cd.QuoteId = q.Id
left outer join Surcharges s on s.ContainerDetailId = cd.Id
where us.SearchDate between @beginDate and @endDate

给定@beginDate和@endDate的某些值,我有一个搜索需要30秒才能返回大约100K行。

最终目标是填充一些具有父子孩子关系的对象。经过一些实验,我发现我可以通过以下方式大大加快查询速度:

Select *
From UserSearches us
left outer join Quotes q on q.UserSearchId = us.Id and q.QuoteNumber is not null
left outer join ContainerDetails cd on cd.QuoteId = q.Id
where us.SearchDate between @beginDate and @endDate

Select cd.Id into #cdIds
From UserSearches us
left outer join Quotes q on q.UserSearchId = us.Id and q.QuoteNumber is not null
left outer join ContainerDetails cd on cd.QuoteId = q.Id
where us.SearchDate between @beginDate and @endDate

Select * From Surcharges s
inner join #cdIds on s.ContainerDetailId = #cdIds.Id

DROP TABLE #cdIds

这在10秒内运行,这对我没有意义。当然,首先加入附加费应该更快。

附加费表格包含以下索引:

PK:

ALTER TABLE [dbo].[Surcharges] ADD  CONSTRAINT [PK_dbo.Surcharges] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)

IX1:

CREATE NONCLUSTERED INDEX [IX_Surcharge_ContainerDetailId] ON [dbo].[Surcharges]
(
    [ContainerDetailId] ASC
)
INCLUDE (   [Id],
[Every],
[Single],
[Column],
[About],
[Twelve],
[Of],
[Them],
)

IX2:

CREATE NONCLUSTERED INDEX [IX_ContainerDetailId] ON [dbo].[Surcharges]
(
    [ContainerDetailId] ASC
)

总而言之,为什么对我的附加费进行单独查询比在第一时间加入它们更快?

编辑:以下是执行计划。这些是可以在Sql Studio中打开的.sqlplan文件:

Query 1 - Combined

Query 2 - Seperate

4 个答案:

答案 0 :(得分:8)

了解实际执行计划的内容。

最好在SQL Sentry Plan Explorer

你会看到你的第一个变体在100,276行中有Actual Data Size = 11,272 MB。

first

在第二个变体中,填充临时表的查询在19,665行中仅返回173KB。最后一个查询在87,510行中返回1,685 MB。

second a

second b

11,272 MB 远远超过 1,685 MB

难怪第一个查询速度较慢。

这种差异是由两个因素造成的:

  1. 在第一个版本中,您可以选择UserSearchesQuotesContainerDetails表中的所有列。在第二个版本中,您只从ID中选择ContainerDetails。除了从磁盘读取和通过网络额外字节传输之外,这种差异导致了截然不同的计划。第二个变体不做排序,不进行密钥查找并使用哈希联接而不是嵌套循环。它在Quotes上使用不同的索引。第二种变体使用ContainerDetails上的索引扫描而不是搜索。

  2. 查询会产生不同数量的行,因为第一个变体使用LEFT JOIN和第二个INNER JOIN

  3. 所以,要使它们具有可比性:

    1. 而不是仅使用*列表显式列出您需要的那些列。
    2. 在两种变体中使用INNER JOIN(或LEFT JOINSurcharges
    3. <强>更新

      您的问题是“为什么SQL Server会更快地运行第二个查询”,答案是:因为查询不同并且它们产生不同的结果(不同的行集,不同的列集)。

      现在你要问另一个问题:如何使它们变得一样快速。

      您的两个变体中哪一个产生了您想要的正确结果?我假设它是临时表的第二个变种。

      请注意,我在这里没有回答如何让它们快速。我在这里回答如何让它们变得一样。

      以下单个查询应该与使用临时表的第二个变量生成完全相同的结果,但没有显式临时表。我希望它的性能与你的第二个临时表类似。我故意用CTE写它来复制你的变体的结构与临时表,虽然很容易重写它没有。无论如何,优化器都足够聪明。

      WITH
      CTE
      AS
      (
          Select cd.Id
          From
              UserSearches us
              left outer join Quotes q on q.UserSearchId = us.Id and q.QuoteNumber is not null
              left outer join ContainerDetails cd on cd.QuoteId = q.Id
          where
              us.SearchDate between @beginDate and @endDate
      )
      Select *
      From
          Surcharges s
          inner join CTE on s.ContainerDetailId = CTE.Id
      ;
      

答案 1 :(得分:3)

好吧,我在查询本身以及计划中看到的内容似乎有两个很大的差异。

首先,可能最有影响力的是,在您使用临时表的第二个版本中,对Surcharges表的最终查询是INNER JOINing而不是您在原始查询中使用的LEFT JOIN运算符。我不确定哪个版本是准确的,但根据计划信息(第一个版本为1860万,第二个版本为510万),返回记录数量的差异似乎非常高。如果您将第一个版本更改为附加费表中的INNER JOIN,您是否在持续时间方面看到类似的结果?

其次,并且影响力可能较小,您的第二个版本会在批处理的select ... into部分中为您提供并行执行。没有看到更多的东西,我可能不敢评论为什么会这样,但它是一个潜在的差异化因素。

我从第一个撰稿人开始,看看你最终结束了什么,并从那里开始。

编辑:

为了帮助澄清这些评论,请尝试将您的第一个查询更改为此并附上查询计划/查看结果/持续时间与临时表/选择...到版本:

Select *
From UserSearches us
left outer join Quotes q on q.UserSearchId = us.Id and q.QuoteNumber is not null
left outer join ContainerDetails cd on cd.QuoteId = q.Id
INNER join Surcharges s on s.ContainerDetailId = cd.Id
where us.SearchDate between @beginDate and @endDate

这应该会给你一个与第二个版本差不多相似的持续时间 - 如果还没有,请附上该版本的查询计划。

答案 2 :(得分:1)

但你不是在比较苹果和苹果 第一个是左3 第二个是2个左边和1个内部连接
在第二个结果是分裂

试试这个 将@beginDate和@endDate之间的我们.SearchDate移动到连接中 我怀疑它正在进行大规模的连接并且过滤最后一次 让日期过滤器提前发生

color #FFF  <- good

color ##FFF  <- easy to miss, will cause very unhelpful 'eos' message at file end

快速搜索对我没有意义

那些左连接对此完全没有任何意义 左边做的就是返回cd.ID = null

Select *
  From UserSearches us
  left outer join Quotes q 
        on q.UserSearchId = us.Id 
       and q.QuoteNumber is not null 
       and us.SearchDate between @beginDate and @endDate
  left outer join ContainerDetails cd 
        on cd.QuoteId = q.Id
  left outer join Surcharges s 
        on s.ContainerDetailId = cd.Id

如果您只想要附加费

Select cd.Id into #cdIds
From UserSearches us
left outer join Quotes q on q.UserSearchId = us.Id and q.QuoteNumber is not null
left outer join ContainerDetails cd on cd.QuoteId = q.Id
where us.SearchDate between @beginDate and @endDate

答案 3 :(得分:0)

  1. 您是否曾尝试使用&lt; =和&gt; =而不是。
  2. 还在SearchDate上创建非聚集索引。
  3. 在proc中声明局部变量以避免参数嗅探
  4. 而不是*,请指定您需要的列。
  5. 您确定,左连接是否正常而非内连接。
  6. 像你的proc一样, 声明@ beginDate1 datetime = @ beginDate 声明@ endDate1 datetime = @ endDate

    试试这个,

    ;With CTE as
    (
    Select us.Id
    From UserSearches us
    where us.SearchDate >= @beginDate1 and us.SearchDate <= @endDate1
    )
    
    Select us.id,q.col1,cd.col2
    From CTE us
    left outer join Quotes q on q.UserSearchId = us.Id and q.QuoteNumber is not null
    left outer join ContainerDetails cd on cd.QuoteId = q.Id
    left outer join Surcharges s on s.ContainerDetailId = cd.Id