【问题标题】:Why does the order of join clauses affect the query plan in SQL Server?为什么连接子句的顺序会影响 SQL Server 中的查询计划?
【发布时间】:2010-11-14 13:24:03
【问题描述】:

我正在 SQL Server 2000(和 2005)中构建一个视图,我注意到连接语句的顺序极大地影响了查询的执行计划和速度。

select      sr.WTSASessionRangeID,
            -- bunch of other columns
from        WTSAVW_UserSessionRange us
inner join  WTSA_SessionRange sr on sr.WTSASessionRangeID = us.WTSASessionRangeID
left outer join WTSA_SessionRangeTutor srt on srt.WTSASessionRangeID = sr.WTSASessionRangeID
left outer join WTSA_SessionRangeClass src on src.WTSASessionRangeID = sr.WTSASessionRangeID
left outer join WTSA_SessionRangeStream srs on srs.WTSASessionRangeID = sr.WTSASessionRangeID
--left outer join MO_Stream ms on ms.MOStreamID = srs.MOStreamID
left outer join WTSA_SessionRangeEnrolmentPeriod srep on srep.WTSASessionRangeID = sr.WTSASessionRangeID
left outer join WTSA_SessionRangeStudent stsd on stsd.WTSASessionRangeID = sr.WTSASessionRangeID
left outer join WTSA_SessionSubrange ssr on ssr.WTSASessionRangeID = sr.WTSASessionRangeID
left outer join WTSA_SessionSubrangeRoom ssrr on ssrr.WTSASessionSubrangeID = ssr.WTSASessionSubrangeID
left outer join MO_Stream ms on ms.MOStreamID = srs.MOStreamID

在 SQL Server 2000 上,上述查询始终生成成本为 946 的计划。如果我在查询中间取消注释 MO_Stream 连接并注释掉底部的连接,则成本降至 263。执行速度下降因此。我一直认为查询优化器会适当地解释查询而不考虑连接顺序,但似乎顺序很重要。

既然顺序 似乎很重要,我应该遵循什么连接策略来编写更快的查询?

(顺便说一句,在 SQL Server 2005 上,使用几乎相同的数据,查询计划成本分别为 0.675 和 0.631。)

编辑:在 SQL Server 2000 上,以下是分析的统计信息:

  • 946-cost query: 9094ms CPU, 5121 reads, 0 writes, 10123ms duration
  • 263-cost query: 172ms CPU, 7477 reads, 0 writes, 170ms duration

编辑:这是表格的逻辑结构。

SessionRange ---+--- SessionRangeTutor
                |--- SessionRangeClass
                |--- SessionRangeStream --- MO_Stream
                |--- SessionRangeEnrolmentPeriod
                |--- SessionRangeStudent
                +----SessionSubrange --- SessionSubrangeRoom

编辑:感谢 Alex 和 gbn 为我指明了正确的方向。我还找到了this question

这是新的查询:

select sr.WTSASessionRangeID    // + lots of columns

from WTSAVW_UserSessionRange us
inner join WTSA_SessionRange sr on sr.WTSASessionRangeID = us.WTSASessionRangeID
left outer join WTSA_SessionRangeTutor srt on srt.WTSASessionRangeID = sr.WTSASessionRangeID
left outer join WTSA_SessionRangeClass src on src.WTSASessionRangeID = sr.WTSASessionRangeID
left outer join WTSA_SessionRangeEnrolmentPeriod srep on srep.WTSASessionRangeID = sr.WTSASessionRangeID
left outer join WTSA_SessionRangeStudent stsd on stsd.WTSASessionRangeID = sr.WTSASessionRangeID

// SessionRangeStream is a many-to-many mapping table between SessionRange and MO_Stream
left outer join (
    WTSA_SessionRangeStream srs
    inner join MO_Stream ms on ms.MOStreamID = srs.MOStreamID
) on srs.WTSASessionRangeID = sr.WTSASessionRangeID

// SessionRanges MAY have Subranges and Subranges MAY have Rooms
left outer join (
    WTSA_SessionSubrange ssr    
    left outer join WTSA_SessionSubrangeRoom ssrr on ssrr.WTSASessionSubrangeID = ssr.WTSASessionSubrangeID
) on ssr.WTSASessionRangeID = sr.WTSASessionRangeID

SQLServer2000 成本:24.9

【问题讨论】:

    标签: sql-server sql-server-2005 optimization join sql-server-2000


    【解决方案1】:

    我不得不不同意之前的所有答案,原因很简单:如果您更改左连接的顺序,您的查询在逻辑上是不同的,因此它们会产生不同的结果集。自己看:

    SELECT 1 AS a INTO #t1
    UNION ALL SELECT 2
    UNION ALL SELECT 3
    UNION ALL SELECT 4;
    
    SELECT 1 AS b INTO #t2
    UNION ALL SELECT 2;
    
    SELECT 1 AS c INTO #t3
    UNION ALL SELECT 3;
    
    SELECT a, b, c 
    FROM #t1 LEFT JOIN #t2 ON #t1.a=#t2.b
      LEFT JOIN #t3 ON #t2.b=#t3.c
    ORDER BY a;
    
    SELECT a, b, c 
    FROM #t1 LEFT JOIN #t3 ON #t1.a=#t3.c
      LEFT JOIN #t2 ON #t3.c=#t2.b
    ORDER BY a;
    
    a           b           c
    ----------- ----------- -----------
    1           1           1
    2           2           NULL
    3           NULL        NULL
    4           NULL        NULL
    
    (4 row(s) affected)
    
    a           b           c
    ----------- ----------- -----------
    1           1           1
    2           NULL        NULL
    3           NULL        3
    4           NULL        NULL
    

    【讨论】:

    • 这取决于表的结构。您对 T1-T2、T2-T3 的情况是正确的。在我的情况下,它是 T1-T2,T1-T3。
    • @geofftnz:看我的回答。你不是 T1-T2,T1-T3
    【解决方案2】:

    连接顺序确实会对结果查询产生影响。这在 FROM 的文档中的 BOL 中有记录:

    是一个结果集,它是两个或多个表的乘积。 对于多个连接,使用括号更改连接的自然顺序

    您可以使用连接周围的括号来更改连接顺序(BOL 确实在文档顶部的语法中显示了这一点,但很容易错过)。

    这被称为交叉行为。您还可以使用查询提示 OPTION (FORCE ORDER) 强制执行特定的连接顺序,但这可能会导致所谓的“繁琐计划”,可能对于正在执行的查询来说不是最佳的。

    【讨论】:

      【解决方案3】:

      无论如何,您的查询可能是错误的。亚历克斯是正确的。 Eric 也可能是正确的,但查询是错误的。

      让我们来看看这个子集:

      WTSA_SessionRange sr
      left outer join
      WTSA_SessionSubrange ssr on ssr.WTSASessionRangeID = sr.WTSASessionRangeID
      left outer join
      WTSA_SessionSubrangeRoom ssrr on ssrr.WTSASessionSubrangeID = ssr.WTSASessionSubrangeID
      

      您正在将 WTSA_SessionSubrangeRoom 加入 WTSA_SessionSubrange。您可能没有来自 WTSA_SessionSubrange 的行。

      连接应该是这样的:

      WTSA_SessionRange sr
      left outer join
      (SELECT WTSASessionRangeID, columns I need
      FROM
          WTSA_SessionSubrange ssr
          left outer join
          WTSA_SessionSubrangeRoom ssrr on ssrr.WTSASessionSubrangeID = ssr.WTSASessionSubrangeID
      ) foo on foo.WTSASessionRangeID = sr.WTSASessionRangeID
      

      这就是连接顺序影响结果的原因,因为它是一个不同的查询,从声明的角度来说。

      您还需要更改 MO_StreamWTSA_SessionRangeStream 加入。

      【讨论】:

      • 什么情况下会导致这两个查询返回不同的结果?
      • 但是在 T1-T2 T2-T3 的情况下,如果 T2 和 T3 之间存在外键关系,使得 T3 中的行如果不引用 T2 中的行就不能存在,这还有关系吗?
      • 是的。这是因为 value-null-null 或 value-value-null 类型的情况。
      • (对现有代码的可怕下沉感觉)我重新格式化了我的查询并将其添加到问题中。这在逻辑上与您的子查询相同吗?
      【解决方案4】:

      几年前在 DevConnections 上,一个关于 SQL Server 性能的会议指出 (a) 外连接的顺序确实很重要,并且 (b) 当一个查询有很多连接时,它不会在创建之前查看所有连接对计划的决定。如果您知道您有有助于加快查询速度的联接,它们应该位于 FROM 列表的早期(如果可以的话)。

      【讨论】:

        【解决方案5】:

        优化包含 JOIN 的查询的一般策略是查看您的数据模型和数据,并尝试确定哪些 JOIN 可以最快地减少必须考虑的记录数量。必须考虑的记录越少,查询运行的速度就越快。服务器通常也会生成更好的查询计划。

        除了上述优化之外,还要确保 JOIN 中使用的所有字段都被索引

        【讨论】:

          【解决方案6】:

          这取决于哪个连接字段被索引 - 如果它必须对第一个字段进行表扫描,但在第二个字段上使用索引,那么它很慢。如果您的第一个连接字段是索引,它会更快。我的猜测是 2005 年通过确定索引字段并首先执行这些字段来更好地优化它

          【讨论】:

            【解决方案7】:

            显然,SQL Server 2005 的优化器比 SQL Server 2000 的优化器好很多。

            但是,您的问题有很多道理。外连接会导致执行因顺序而有很大差异(内连接往往被优化为最有效的路线,但同样,顺序很重要)。如果你考虑一下,当你建立左连接时,你需要弄清楚左边到底是什么。因此,必须先计算每个连接,然后才能完成其他连接。它变成顺序的,而不是并行的。现在,显然,您可以采取一些措施来解决这个问题(例如索引、视图等)。但是,重点是:表需要知道左边是什么,然后才能进行左外连接。而且,如果您只是继续添加连接,那么您对左侧的确切内容会越来越抽象(尤其是如果您使用连接表作为左表!)。

            但是,使用内部联接,您可以将它们并行化很多,因此就顺序而言,差别不大。

            【讨论】:

            • 感谢 Eric 的投入 - 我将进行更多重新安排,看看能否进一步降低成本。所有用于连接的列都有索引。不幸的是,数据的性质意味着我必须为此使用外连接。
            • “很明显,SQL Server 2005 的优化器比 SQL Server 2000 的优化器好很多。”这是轻描淡写。不仅 SQL Server 2000 查询优化器很弱(在我看来,与 2000 版的其余部分一样),它似乎经常过度优化,做出错误的假设,从而导致数据不正确。一旦您越过某个复杂性阈值,视图的联合,或包含联合的视图或包含视图(!)的视图可能会触发这种白痴。我一直无法准确指出问题的本质,但我经常被它所困扰。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-05-31
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多