【问题标题】:Why is this non-correlated query so slow?为什么这个不相关的查询这么慢?
【发布时间】:2012-06-06 10:56:40
【问题描述】:

我有这个问题...

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 计划中有三个聚集索引扫描,每个扫描的成本为 100%! (执行计划为here

谁能告诉我为什么内部的DISTINCTS 让这个查询慢得多(但只有在用作IN 的主题时...)?

更新

抱歉,我花了一段时间才制定这些执行计划......

Query 1

Query 2 (The slow one)

Query 3 - No Inner Distincts

【问题讨论】:

  • 如果您发布 XML 计划(供下载),您将获得更好的响应
  • 因此,在将其与 IN 子查询一起使用时,请删除 DISTINCT。问题解决了:)
  • 你是怎么写这篇文章的——为什么你要自己做 3 次不同的工作,而只是切换到union(从union all 会自动执行该操作)?跨度>
  • 我认为这更适合dba.stackexchange.com,你可能会在那里得到更好的答案。您可以将其标记为迁移。
  • 实际执行计划而不是估计计划会更好,并且对于查询(第 2 次和第 3 次)

标签: sql-server query-optimization


【解决方案1】:

老实说,我认为归根结底是这样一个事实,就关系运算符而言,您在那里有一个无缘无故的巴洛克式查询,并且 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 这是一个相关的子查询吗?这不是我对相关子查询的理解。为此,我将为外部 AST_tblAttributes 设置别名,然后在内部表中引用该外部表中的列 - 但是我不这样做......
  • 你是对的,这个语法没有使用显式关联。但是,this.intAttributeID IN (...) 使其成为半连接,并等效于显式关联形式 EXISTS (.... WHERE this.intAttributeID = that.intAttributeID)
【解决方案2】:

埃尔龙诺科

首先是一个可能的解释:

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

如果您共享了不同变体的查询计划(请以文件形式),我想我应该能够让您了解这里发生了什么。它还可以让我们验证解释。

【讨论】:

    【解决方案3】:

    编辑:每个 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
    

    【讨论】:

    • 感谢您的回答罗素。我用的是2008是的。不幸的是,您的查询没有执行。但是我不相信您已经理解我的问题 - 我知道如何更快地进行查询。正如上面其他人所说,我可以从查询中删除所有 DISTINCTS。我想知道的是为什么 SQLServer 没有尽可能有效地优化。
    • 哦,对了,回答问题(需要咖啡)。答案已编辑,如果有帮助的话。
    • @RussellFox,每个 DISTINCT 关键字可能添加一个新的排序节点,这取决于 a) DISTINCT 出现的位置和 b) SQL 是否可以检测到将一定是不同的。
    • 但我不明白的是,第一个查询(在我的问题的顶部)在 1 秒内执行(其中有 3 个 DISTINCTS)。它返回 5 个 ID - 如果我只是说例如 IN (23,145,167,180,190),那么完整的查询执行得非常快。但是,如果我使用第一个查询作为 IN(..) 的主题,则需要 3 分钟 - 即使他们正在做完全相同的事情......
    • 我怀疑外部查询必须检查内部查询中每个 attx.intAttributeID 的区别,因此外部查询中的每一行都会调用排序。不过,只是猜测。
    猜你喜欢
    • 1970-01-01
    • 2011-03-11
    • 2020-03-19
    • 2014-03-12
    • 2011-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多