为什么join子句的顺序会影响SQL Server中的查询计划?

时间:2009-07-28 01:27:06

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

我正在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

7 个答案:

答案 0 :(得分:6)

我必须不同意之前的所有答案,原因很简单:如果更改左连接的顺序,则查询在逻辑上会有所不同,因此它们会生成不同的结果集。亲眼看看:

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

答案 1 :(得分:3)

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

  

< joined_table>

     

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

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

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

答案 2 :(得分:2)

显然,SQL Server 2005优化器比SQL Server 2000优化器要好得多。

然而,你的问题有很多道理。外连接将导致执行根据顺序变化很大(内部连接倾向于优化到最有效的路径,但同样,顺序很重要)。如果你考虑一下,当你建立左连接时,你需要弄清楚左边是什么。因此,必须在每个其他连接完成之前计算每个连接。它变得顺序,而不是平行。现在,很明显,你可以采取一些措施来解决这个问题(例如索引,视图等)。但是,重点是:表格需要知道左边的内容才能进行左外连接。如果你只是继续添加连接,你会得到越来越多的抽象,正好在左边(特别是如果你使用连接表作为左表!)。

但是,对于内部联接,您可以将这些内容并行化,因此就订单而言,它们之间没有太大的差别。

答案 3 :(得分:2)

优化包含JOIN的查询的一般策略是查看数据模型和数据,并尝试确定哪些JOIN将减少必须被认为最快的记录数。必须考虑的记录越少,查询运行得越快。服务器通常也会产生更好的查询计划。

与上述优化一起确保JOIN中使用的任何字段都已编入索引

答案 4 :(得分:2)

无论如何,您的查询可能是错误的。亚历克斯是对的。 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加入。

答案 5 :(得分:1)

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

来更好地优化它

答案 6 :(得分:1)

几年前在DevConnections上,关于SQL Server性能的会话表明:(a)外连接的顺序很重要,(b)当查询有很多连接时,它在制作之前不会查看所有连接对计划的决定。如果您知道有连接可以帮助加快查询速度,那么它们应该早在FROM列表中(如果可以的话)。