为什么这个非相关查询这么慢?

时间:2012-06-06 10:56:41

标签: sql-server query-optimization

我有这个问题......

SELECT Distinct([TargetAttributeID]) FROM
    (SELECT distinct att1.intAttributeID as [TargetAttributeID]
        FROM AST_tblAttributes att1
        INNER JOIN
        AST_lnkProfileDemandAttributes pda
        ON pda.intAttributeID=att1.intAttributeID AND pda.intProfileID = @intProfileID

    union all

    SELECT distinct ca2.intAttributeID as [TargetAttributeID] FROM
        AST_lnkCapturePolicyAttributes ca2
        INNER JOIN
        AST_lnkEmployeeCapture ec2 ON ec2.intAdminCaptureID = ca2.intAdminCaptureID AND ec2.intTeamID = 57
        WHERE ec2.dteCreatedDate >= @cutoffdate) x

Execution Plan for the above query

两个内部区别分别是32和10,000行。此查询返回5行,并在1秒内执行。

如果我然后使用此查询的结果作为IN的主题,那么......

SELECT attx.intAttributeID,attx.txtAttributeName,attx.txtAttributeLabel,attx.txtType,attx.txtEntity FROM
    AST_tblAttributes attx WHERE attx.intAttributeID 
    IN
    (SELECT Distinct([TargetAttributeID]) FROM
    (SELECT Distinct att1.intAttributeID as [TargetAttributeID]
        FROM AST_tblAttributes att1
        INNER JOIN
        AST_lnkProfileDemandAttributes pda
        ON pda.intAttributeID=att1.intAttributeID AND pda.intProfileID = @intProfileID
    union all
    SELECT  Distinct ca2.intAttributeID as [TargetAttributeID] FROM
        AST_lnkCapturePolicyAttributes ca2
        INNER JOIN
        AST_lnkEmployeeCapture ec2 ON ec2.intAdminCaptureID = ca2.intAdminCaptureID AND ec2.intTeamID = 57
        WHERE ec2.dteCreatedDate >= @cutoffdate) x)

Execution Plan for the above query

然后需要3分钟!如果我只是获取查询结果并手动执行IN,那么它会再次快速返回。

但是,如果我删除了两个内部DISTINCTS ....

SELECT attx.intAttributeID,attx.txtAttributeName,attx.txtAttributeLabel,attx.txtType,attx.txtEntity FROM
    AST_tblAttributes attx WHERE attx.intAttributeID 
    IN
    (SELECT Distinct([TargetAttributeID]) FROM
    (SELECT att1.intAttributeID as [TargetAttributeID]
        FROM AST_tblAttributes att1
        INNER JOIN
        AST_lnkProfileDemandAttributes pda
        ON pda.intAttributeID=att1.intAttributeID AND pda.intProfileID = @intProfileID
    union all
    SELECT ca2.intAttributeID as [TargetAttributeID] FROM
        AST_lnkCapturePolicyAttributes ca2
        INNER JOIN
        AST_lnkEmployeeCapture ec2 ON ec2.intAdminCaptureID = ca2.intAdminCaptureID AND ec2.intTeamID = 57
        WHERE ec2.dteCreatedDate >= @cutoffdate) x)

Execution Plan for the above query

..然后它会在一秒钟之内回来。

什么是SQL Server思维?难道不能弄清楚它可以执行两个子查询并将结果用作IN的主题。它似乎与相关的子查询一样慢,但它没有相关性!!!

在Show Estimate Execution计划中,有三个Clustered Index Scans,每个扫描成本为100%! (执行计划为here

有人可以告诉我为什么内部DISTINCTS使这个查询变得如此慢(但只有当用作IN的主题时才会...)

更新

很抱歉,我花了一些时间来完成这些执行计划...

Query 1

Query 2 (The slow one)

Query 3 - No Inner Distincts

3 个答案:

答案 0 :(得分:9)

老实说,我认为这归结为这样一个事实:就关系运算符而言,你在那里有一个无偿的巴洛克式查询,并且SQL Server在它允许自己找到的时间内停止搜索备用执行计划。

在计划编译的解析和绑定阶段之后,SQL Server将逻辑变换应用于生成的树,估计每个树的成本,并选择成本最低的树。它并没有耗尽所有可能的转换,就像它在给定窗口内可以计算的数量一样多。所以,据推测,它在到达一个好的计划之前已经烧掉了那个窗口,并且它是在AST_tblAttributes上添加外部半自连接,将其推到边缘。

如何免费巴洛克式?好吧,首先,有这个(简化为降噪):

select distinct intAttributeID from (
   select distinct intAttributeID from AST_tblAttributes ....
   union all
   select distinct intAttributeID from AST_tblAttributes ....
   )

连接两组,并投射独特的元素?原来那里有运算符,它被称为UNION。因此,在计划编译和足够的逻辑转换期间有足够的时间,SQL Server将实现您真正的含义:

select intAttributeID from AST_tblAttributes ....
union
select intAttributeID from AST_tblAttributes ....

但是等等,你把它放在一个相关的子查询中。嗯,相关子查询是半连接,正确的关系不需要在半连接中进行逻辑推理。因此,SQL Server可以在逻辑上重写查询:

select * from AST_tblAttributes
where intAttributeID in (
  select intAttributeID from AST_tblAttributes ....
  union all
  select intAttributeID from AST_tblAttributes ....
  )

然后进行物理计划选择。但要实现这一目标,必须首先看看这个问题,这可能不属于优化窗口。


编辑:

实际上,为自己探索这个问题并证实上述推测的方法是将两个版本的查询放在同一个窗口中,并将估计的执行计划并排比较(SSMS中的Ctrl-L)。保持原样,编辑另一个,看看有什么变化。

您将看到一些替代形式被视为逻辑上等效并生成相同的良好计划,而其他形式则生成不太理想的计划,因为您需要优化器。**

然后,您可以使用SET STATISTICS IO ONSET STATISTICS TIME ON来观察SQL Server执行查询所执行的实际工作量:

SET STATISTICS IO ON
SET STATISTICS TIME ON

SELECT ....
SELECT ....

SET STATISTICS IO OFF
SET STATISTICS TIME OFF

输出将显示在消息窗格中。

**或者不 - 如果他们都生成相同的计划,但实际执行时间仍然像你说的那样变化,其他可能正在发生 - 这并非闻所未闻。尝试比较实际执行计划并从那里开始。

答案 1 :(得分:2)

El Ronnoco

首先是一个可能的解释:

您说:“此查询返回 5行,并在1秒内执行。”但是返回了多少行ESTIMATE?如果估计非常大,那么使用查询作为IN部分的一部分可能会导致您扫描整个外部的AST_tblAttributes,而不是索引寻找它(这可以解释很大的区别)

如果您分享了不同变体的查询计划(请作为文件),我想我应该能够让您了解这里的内容。它还允许我们验证解释。

答案 2 :(得分:1)

编辑:每个DISTINCT关键字都会在查询计划中添加一个新的排序节点。基本上,通过在那里使用其他DISTINCT,您迫使SQL一次又一次地重新排序整个表,以确保它不会返回重复项。每个这样的操作可以使查询的成本翻两番。 Here's对DISTINCT操作员可能产生的影响进行了详细的审查,意图是无意的。我自己也被这个咬了。


您使用的是SQL 2008吗?如果是这样,您可以尝试这样做,将DISTINCT工作放入CTE然后加入主表。我发现CTE非常快:

WITH DistinctAttribID
AS
(
SELECT Distinct([TargetAttributeID]) 
FROM (
    SELECT distinct att1.intAttributeID as [TargetAttributeID] 
        FROM AST_tblAttributes att1 
        INNER JOIN 
        AST_lnkProfileDemandAttributes pda 
        ON pda.intAttributeID=att1.intAttributeID AND pda.intProfileID = @intProfileID 

    UNION ALL 

    SELECT distinct ca2.intAttributeID as [TargetAttributeID] FROM 
        AST_lnkCapturePolicyAttributes ca2 
        INNER JOIN 
        AST_lnkEmployeeCapture ec2 ON ec2.intAdminCaptureID = ca2.intAdminCaptureID AND ec2.intTeamID = 57 
        WHERE ec2.dteCreatedDate >= @cutoffdate
) x

SELECT attx.intAttributeID,
    attx.txtAttributeName,
    attx.txtAttributeLabel,
    attx.txtType,
    attx.txtEntity 
FROM AST_tblAttributes attx 
JOIN DistinctAttribID attrib
    ON attx.intAttributeID = attrib.TargetAttributeID