我应该在 SQL Server 中嵌套依赖外部联接吗?

Mat*_*hew 9 performance join sql-server-2008-r2

我听到了关于这方面的混合信息,希望得到权威或专家的意见。

如果我有多个LEFT OUTER JOINs ,每个都依赖于最后一个,嵌套它们更好吗?

对于一个人为的例子,JOINtoMyParent取决于JOINto MyChildhttp : //sqlfiddle.com/#!3/31022/5

SELECT
    {columns}
FROM
    MyGrandChild AS gc
LEFT OUTER JOIN
    MyChild AS c
        ON c.[Id] = gc.[ParentId]
LEFT OUTER JOIN
    MyParent AS p
        ON p.[id] = c.[ParentId]
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

http://sqlfiddle.com/#!3/31022/7相比

SELECT
    {columns}
FROM
    MyGrandChild AS gc
LEFT OUTER JOIN
    (
    MyChild AS c            
    LEFT OUTER JOIN
        MyParent AS p
            ON p.[id] = c.[ParentId]
    )
    ON c.[Id] = gc.[ParentId]
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

如上所示,这些在 SS2k8 中产生不同的查询计划

Mar*_*ith 3

这绝对不是一个规范的答案,但我注意到,对于 SQL Fiddle 中显示的嵌套循环查询计划,可以使用提示将计划从查询 2 应用于查询 1,USE PLAN但尝试反向操作会失败

查询处理器无法生成查询计划,因为 USE PLAN 提示包含无法验证为查询合法的计划。删除或替换使用计划提示。为了获得计划强制成功的最佳可能性,请验证 USE PLAN 提示中提供的计划是否是 SQL Server 针对同一查询自动生成的计划。

禁用优化器转换规则 ReorderLOJN也会阻止之前成功的计划提示成功。

对大量数据进行的实验表明,SQL Server 当然也能够进行自然转换(A LOJ B) LOJ CA LOJ (B LOJ C)但我没有看到任何证据表明相反的情况也是如此。

第一个查询比第二个查询执行得更好的一个非常人为的情况是

DROP TABLE  MyGrandChild , MyChild,  MyParent

CREATE TABLE MyParent
(Id int)

CREATE TABLE MyChild
(Id int PRIMARY KEY
,ParentId int,
Filler char(8000) NULL)

CREATE TABLE MyGrandChild
(Id int
,ParentId int)

INSERT INTO MyChild
                      (Id, ParentId)
SELECT TOP (100000) ROW_NUMBER() OVER (ORDER BY @@SPID),
                     ROW_NUMBER() OVER (ORDER BY @@SPID)    
FROM master..spt_values  v1, master..spt_values                  

INSERT INTO MyGrandChild
                      (Id, ParentId)
OUTPUT INSERTED.Id INTO MyParent
SELECT TOP (3000) Id, Id AS ParentId
FROM MyChild
ORDER BY Id

SET STATISTICS IO ON;
SET STATISTICS TIME ON;

SELECT gc.Id       AS gcId,
       gc.ParentId AS gcpId,
       c.Id        AS cId,
       c.ParentId  AS cpId,
       p.Id        AS pId
FROM   MyGrandChild AS gc
       LEFT OUTER JOIN MyChild AS c
         ON c.[Id] = gc.[ParentId]
       LEFT OUTER JOIN MyParent AS p
         ON p.[Id] = c.[ParentId]

SELECT gc.Id       AS gcId,
       gc.ParentId AS gcpId,
       c.Id        AS cId,
       c.ParentId  AS cpId,
       p.Id        AS pId
FROM   MyGrandChild AS gc
       LEFT OUTER JOIN( MyChild AS c
                        LEFT OUTER JOIN MyParent AS p
                          ON p.[Id] = c.[ParentId])
         ON c.[Id] = gc.[ParentId] 
Run Code Online (Sandbox Code Playgroud)

哪个给出了计划

在此输入图像描述

对我来说,查询 1 的运行时间为 108 毫秒,而查询 2 的运行时间为 1,163 毫秒。

查询1

Table 'Worktable'. Scan count 0, logical reads 0 
Table 'MyChild'. Scan count 0, logical reads 9196
Table 'MyGrandChild'. Scan count 1, logical reads 7
Table 'MyParent'. Scan count 1, logical reads 5
Run Code Online (Sandbox Code Playgroud)

查询2

Table 'MyParent'. Scan count 1, logical reads 15000
Table 'MyChild'. Scan count 0, logical reads 9000 
Table 'MyGrandChild'. Scan count 1, logical reads 7
Run Code Online (Sandbox Code Playgroud)

因此,可以暂时假设第一个(“非嵌套”)语法可能是有益的,因为它允许考虑更多潜在的连接顺序,但我还没有进行足够详尽的测试,无法对此有足够的信心作为一般规则。

完全有可能提出查询 2 性能更好的反例。尝试两者并查看执行计划。