主键上的自连接

pku*_*rov 9 sql-server optimization sql-server-2014

考虑这个由N自连接组成的查询:

select
    t1.*
from [Table] as t1
join [Table] as t2 on
    t1.Id = t2.Id
-- ...
join [Table] as tN on
    t1.Id = tN.Id
Run Code Online (Sandbox Code Playgroud)

它生成一个执行计划,其中包含 N 次聚集索引扫描和 N-1 次合并连接。

老实说,我看不出有任何理由不优化所有连接并仅执行一次聚集索引扫描,即将原始查询优化为:

select
    t1.*
from [Table] as t1
Run Code Online (Sandbox Code Playgroud)

问题

  • 为什么连接没有被优化掉?
  • 说每个连接都不会改变结果集在数学上是不正确的吗?

测试:

  • 源服务器版本:SQL Server 2014 (12.0.4213)
  • 源数据库引擎版:Microsoft SQL Server 标准版
  • 源数据库引擎类型:独立 SQL Server
  • 兼容级别:SQL Server 2008 (100)

查询没有意义;它刚刚出现在我的脑海中,我现在对它很好奇。

这是表创建和 3 个查询的小提琴:使用inner join's、使用left join's 和混合。您也可以在那里查看执行计划。

似乎left join在结果执行计划中消除了inner joins而s 则没有。不过还是不明白为什么

ype*_*eᵀᴹ 18

首先,让我们假设这(id)是表的主键。在这种情况下,是的,连接(可以证明)是多余的,可以消除。

现在这只是理论 - 或数学。为了让优化器进行实际的消除,必须将理论转换为代码并添加到优化器的优化/重写/消除套件中。为此,(DBMS) 开发人员必须认为它对效率有很大好处,并且这是一个足够常见的情况。

就个人而言,它听起来不像一个(足够常见)。查询 - 正如您所承认的 - 看起来很愚蠢,审阅者不应该让它通过审阅,除非它得到改进并删除了冗余连接。

也就是说,确实存在消除确实发生的类似查询。Rob Farley 有一篇非常好的相关博客文章:SQL Server 中的 JOIN 简化

在我们的例子中,我们要做的就是将LEFT连接更改为连接。请参阅dbfiddle.uk。在这种情况下,优化器知道可以安全地删除连接而不会改变结果。(简化逻辑非常通用,不是自连接的特殊情况。)

当然,在原始查询中,删除INNER连接也不可能改变结果。但是在主键上自连接根本不常见,因此优化器没有实现这种情况。然而,连接(或左连接)是常见的,其中连接的列是其中一个表的主键(并且通常有外键约束)。这导致了消除连接的第二个选项:添加(自引用!)外键约束:

ALTER TABLE "Table"
    ADD FOREIGN KEY (id) REFERENCES "Table" (id) ;
Run Code Online (Sandbox Code Playgroud)

瞧,连接被消除了!(在同一个小提琴中测试):这里

create table docs
(id int identity primary key,
 doc varchar(64)
) ;
GO
Run Code Online (Sandbox Code Playgroud)
?
insert
into docs (doc)
values ('Enter one batch per field, don''t use ''GO''')
     , ('Fields grow as you type')
     , ('Use the [+] buttons to add more')
     , ('See examples below for advanced usage')
  ;
GO
Run Code Online (Sandbox Code Playgroud)
4 行受影响
--------------------------------------------------------------------------------
-- Or use XML to see the visual representation, thanks to Justin Pealing and
-- his library: https://github.com/JustinPealing/html-query-plan
--------------------------------------------------------------------------------
set statistics xml on;
select d1.* from docs d1 
    join docs d2 on d2.id=d1.id
    join docs d3 on d3.id=d1.id
    join docs d4 on d4.id=d1.id;
set statistics xml off;
GO
Run Code Online (Sandbox Code Playgroud)
身份证 | 文档                                      
-: | :----------------------------------------
 1 | 每个字段输入一批,不要使用“GO”
 2 | 字段随着您键入而增长                  
 3 | 使用 [+] 按钮添加更多          
 4 | 有关高级用法,请参见下面的示例    

在此处输入图片说明

--------------------------------------------------------------------------------
-- Or use XML to see the visual representation, thanks to Justin Pealing and
-- his library: https://github.com/JustinPealing/html-query-plan
--------------------------------------------------------------------------------
set statistics xml on;
select d1.* from docs d1 
    left join docs d2 on d2.id=d1.id
    left join docs d3 on d3.id=d1.id
    left join docs d4 on d4.id=d1.id;
set statistics xml off;
GO
Run Code Online (Sandbox Code Playgroud)
身份证 | 文档                                      
-: | :----------------------------------------
 1 | 每个字段输入一批,不要使用“GO”
 2 | 字段随着您键入而增长                  
 3 | 使用 [+] 按钮添加更多          
 4 | 有关高级用法,请参见下面的示例    

在此处输入图片说明

alter table docs
  add foreign key (id) references docs (id) ;
GO
Run Code Online (Sandbox Code Playgroud)
?
--------------------------------------------------------------------------------
-- Or use XML to see the visual representation, thanks to Justin Pealing and
-- his library: https://github.com/JustinPealing/html-query-plan
--------------------------------------------------------------------------------
set statistics xml on;
select d1.* from docs d1 
    join docs d2 on d2.id=d1.id
    join docs d3 on d3.id=d1.id
    join docs d4 on d4.id=d1.id;
set statistics xml off;
GO
Run Code Online (Sandbox Code Playgroud)
身份证 | 文档                                      
-: | :----------------------------------------
 1 | 每个字段输入一批,不要使用“GO”
 2 | 字段随着您键入而增长                  
 3 | 使用 [+] 按钮添加更多          
 4 | 有关高级用法,请参见下面的示例    

在此处输入图片说明