Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/83.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql 为什么外部联接比单独查询慢_Sql_Sql Server_Query Optimization_Sql Server 2014 - Fatal编程技术网

Sql 为什么外部联接比单独查询慢

Sql 为什么外部联接比单独查询慢,sql,sql-server,query-optimization,sql-server-2014,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

我有一个查询,基本上如下所示:

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秒才能返回大约10万行的搜索

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

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秒钟,这对我来说毫无意义。当然,首先加入附加费应该更快

附加费表具有以下索引:

主键:

IX1:

IX2:

总而言之,为什么单独查询我的附加费要比一开始就加入它们更快

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


我在查询本身和计划中看到的差异似乎有两大方面

首先,可能是最有影响的,在使用临时表的第二个版本中,对附加费表的最终查询是内部联接,而不是原始查询中使用的左联接运算符。我不确定哪个版本是准确的,但根据计划信息,返回记录的数量差异似乎很大(第一个版本为1860万条,第二个版本为510万条)。如果在“附加费”表中将第一个版本更改为内部联接,在持续时间方面是否会看到类似的结果

其次,可能影响较小的是,您的第二个版本在批处理的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

希望这会给您一个与第二个版本大致相似的持续时间-如果仍然没有,请附上该版本的查询计划。

但您不是在对苹果进行比较
第一个是3左
第二个是2个左连接和1个内连接
在第二种情况下,结果是分裂的

试试这个
将us.SearchDate在@beginDate和@endDate之间向上移动到连接中
我怀疑它正在进行大规模的连接,而最后的过滤
让日期过滤器尽早出现

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
快速搜索对我来说毫无意义

那些左连接对此没有任何作用
左侧所做的就是返回cd.ID=null

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 s.*
     From UserSearches us
     join Quotes q 
       on q.UserSearchId = us.Id 
      and q.QuoteNumber is not null 
      and us.SearchDate between @beginDate and @endDate
     join ContainerDetails cd 
       on cd.QuoteId = q.Id
     join Surcharges s on 
       on s.ContainerDetailId = cd.Id

要了解正在发生的事情,请查看实际执行计划

最好是在家里

您将看到第一个变量在100276行中的实际数据大小为11272MB

在第二个变量中,填充临时表的查询只返回19665行中的173KB。最后一个查询返回87510行中的1685MB

11272MB
远远超过
1685MB
难怪第一个查询速度较慢

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

  • 在第一个变量中,选择
    UserSearches
    Quotes
    ContainerDetails
    表中的所有列。而在第二个变体中,您仅从
    容器详细信息
    中选择
    ID
    。除了从磁盘读取和通过网络传输额外的字节外,这种差异导致了实质上不同的计划。第二个变体不进行排序,不进行键查找,并使用哈希连接而不是嵌套循环。它在
    引号上使用不同的索引。第二种变体在
    集装箱细节上使用索引扫描,而不是搜索

  • 查询产生不同数量的行,因为第一个变量使用
    左连接
    ,第二个变量使用
    内部连接

  • 因此,为了使它们具有可比性:

  • 不要使用
    *
    而是只显式列出您需要的列
  • 在两种变体中使用
    内部联接
    (或
    左侧联接
    附加费

  • 更新

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

    现在你又在问另一个问题:如何使它们保持相同的速度

    您的两个变体中的哪一个产生您想要的正确结果?我假设它是temp table的第二个变体

    请注意,我在这里没有回答如何使它们快速。我在这里回答如何使它们相同

    以下单个查询应生成与第二个变量完全相同的结果,该变量使用临时表,但不使用显式临时表。我希望它的性能与您的第二个变体类似,具有临时表。我特意用CTE编写了它来复制带有临时表的变体的结构,尽管不用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
    ;
    
  • 您是否尝试过使用=而不是between
  • 还可以在SearchDate上创建非聚集索引
  • 还要在进程中声明局部变量,以避免参数嗅探
  • 指定所需的列而不是*
  • 您确定左联接而不是内联接可以吗。
  • 就像你的程序一样, 声明@beginDate1日期时间=@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
    
    ;以CTE为例
    (
    选择我们
    来自我们
    我们在哪里
    
       Select s.*
         From UserSearches us
         join Quotes q 
           on q.UserSearchId = us.Id 
          and q.QuoteNumber is not null 
          and us.SearchDate between @beginDate and @endDate
         join ContainerDetails cd 
           on cd.QuoteId = q.Id
         join Surcharges s on 
           on s.ContainerDetailId = cd.Id
    
    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
    ;
    
    ;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