热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入SQLServer连接(JOIN)系列–part5连接小结

PART1PART2PART3PART4下表总结了前面所提到三个物理连接操作符:例子剩余的内容中,我们会通过例子来看一个查询

PART 1
PART 2
PART 3
PART 4


下表总结了前面所提到三个物理连接操作符:


 

例子

剩余的内容中,我们会通过例子来看一个查询是如何微妙的选择不同的连接类型的。

让我们先建立两个有1000行的表:

create table T1 (a int, b int, x char(200))
 
create table T2 (a int, b int, x char(200))
 
set nocount on
 
declare @i int
 
set @i = 0
 
while @i < 1000
 
  begin
 
    insert T1 values (@i * 2, @i * 5, @i)
 
    insert T2 values (@i * 3, @i * 7, @i)
 
    set @i = @i + 1
 
  end
 

因为我们没有索引,这两个表的简单连接会生成一个hash连接:


select * from T1 join T2 on T1.a = T2.a



  |--Hash Match(Inner Join, HASH:([T1].[a])=([T2].[a]), RESIDUAL:([T2].[a]=[T1].[a]))
       |--Table Scan(OBJECT:([T1]))
       |--Table Scan(OBJECT:([T2]))

 

但是,hash连接有一个不好的地方是会快速的返回查询的前几行。假设我们只想要前10行:


select top 10 * from T1 join T2 on T1.a = T2.a


  |--Top(TOP EXPRESSION:((10)))
       |--Nested Loops(Inner Join, WHERE:([T2].[a]=[T1].[a]))
            |--Table Scan(OBJECT:([T1]))
            |--Table Spool
                 |--Table Scan(OBJECT:([T2]))
 

现在我们得到了一个嵌套循环连接(nested loop join)。我们没有索引,所以该计划会为T1的每一行来扫描T2的所有行,直到我们找到了想要的10行。如果找到这10行需要很多对T2的扫描,这个执行计划的成本就很高。幸运的是,如果你执行该查询,并打开statistics profile,我们可以看到只需要对T2扫描28次。

注意:

可能正如你所料,top运算符返回的前N行后,立即终止它的输入子树。在本例中,N的值为10.
Table spool是一个优化  。在这个例子中,它其实没起多大作用。但是,假设我们用”select T2.a“ 取代”select *”。T2包含了3列,其中还有一个char(200)的列,但我们只需要其中的一个整数列T2.a。因此,我们可以将T2.a进行table spool(假脱机).T2可能只会在每一个page里面存储大概40行,因为每行的大小可能在200bytes左右。扫描T2的所有1000行,可能需要扫描25个page。而spool可以将1000行的列T2.a保存在1到2个page里面。
回到例子中,正如前面提到的,如果我们请求了太多的行,嵌套循环连接成本会变得很高。这时,一个merge join就是更好的选择了。


select top 100 * from T1 join T2 on T1.a = T2.a


  |--Top(TOP EXPRESSION:((100)))
       |--Merge Join(Inner Join, MANY-TO-MANY MERGE:([T2].[a])=([T1].[a]), RESIDUAL:([T2].[a]=[T1].[a]))
            |--Sort(ORDER BY:([T2].[a] ASC))
            |    |--Table Scan(OBJECT:([T2]))
            |--Sort(ORDER BY:([T1].[a] ASC))
                 |--Table Scan(OBJECT:([T1]))

 

相似的,如果我们无论如何需要排序,一个merge join也是很好的选择,因为我们已经需要对至少一个表进行排序了。


select top 10 * from T1 join T2 on T1.a = T2.a
 
order by T1.a
 

该查询的执行计划基本与上面top 100的查询语句一样。

现在,让我们添加一个聚集索引:


create unique clustered index T1a on T1(a)
 

回到最初的连接,我们现在得到了一个多对一的合并连接:


select * from T1 join T2 on T1.a = T2.a


  |--Merge Join(Inner Join, MERGE:([T1].[a])=([T2].[a]), RESIDUAL:([T2].[a]=[T1].[a]))
       |--Clustered Index Scan(OBJECT:([T1].[T1a]), ORDERED FORWARD)
       |--Sort(ORDER BY:([T2].[a] ASC))
            |--Table Scan(OBJECT:([T2]))

 

优化器发现我们已经有一个表进行排序了,可能会决定也对另外一个表也进行排序,然后使用merge join 而不是hash join:

但如果我们在T1的表扫描上添加了一个谓词,我们可以得到hash join:

select * from T1 join T2 on T1.a = T2.a
 
where T1.b < 100


  |--Hash Match(Inner Join, HASH:([T1].[a])=([T2].[a]), RESIDUAL:([T2].[a]=[T1].[a]))
       |--Clustered Index Scan(OBJECT:([T1].[T1a]), WHERE:([T1].<(100)))
       |--Table Scan(OBJECT:([T2]))

 

merge join需要对T1的所有1000行进行排序,而hash join只需要为T1表上满足T1.b < 100的20左右的行构建一个hash表。因此,hash join的执行计划需要更少的内存,也不太可能会溢出。

但如果我们也在查询上添加一个排序,我们又得到了merge join:

select * from T1 join T2 on T1.a = T2.a
 
where T1.b < 100
 
order by T1.a


  |--Merge Join(Inner Join, MERGE:([T1].[a])=([T2].[a]), RESIDUAL:([T2].[a]=[T1].[a]))
       |--Clustered Index Scan(OBJECT:([T1].[T1a]),  WHERE:([T1].[b]<(100)) ORDERED FORWARD)
       |--Sort(ORDER BY:([T2].[a] ASC))
            |--Table Scan(OBJECT:([T2]))

 

回顾下,merge join会保留顺序,所以我们不再需要再进行一个最终排序,来满足查询语句中的排序子语句。hash join不会保留顺序,所以我们可能需要一个额外的排序。如果使用hash join,我们可能会需要两个消耗内存的运算符。(注意到一个消耗内存的运算符不一定就比两个消耗内存的运算符来得好,关键是要看运算符所需要的内存量)。

接着,我们在T2的表扫描上添加一个谓词:


select * from T1 join T2 on T1.a = T2.a
 where T2.b < 100

 

  |--Nested Loops(Inner Join, OUTER REFERENCES:([T2].[a]))
       |--Table Scan(OBJECT:([T2]), WHERE:([T2].[b]<(100)))
       |--Clustered Index Seek(OBJECT:([T1].[T1a]), SEEK:([T1].[a]=[T2].[a]) ORDERED FORWARD)

 

通过减少T2的基数,一个基于索引的嵌套循环连接限制是最好的选择了。

最后,让我们添加一个有10万行的大表T3:

create table T3 (a int, b int, x char(200))
 
declare @i int
 
set @i = 0
 
while @i < 100000
 
  begin
 
    insert T3 values (@i * 5, @i * 11, @i)
 
    set @i = @i + 1
 
  end
 

考虑一个简单的T1和T3的连接:


select * from T1 join T3 on T1.a = T3.a


  |--Hash Match(Inner Join, HASH:([T1].[a])=([T3].[a]), RESIDUAL:([T3].[a]=[T1].[a]))
       |--Clustered Index Scan(OBJECT:([T1].[T1a]))
       |--Table Scan(OBJECT:([T3]))

 

即使我们在T1上有一个索引,我们仍然选择了hash join。回顾下,T1和T2这两个大小相近的表是做merge join。要做一个merge join,我们需要在T1和T3上都进行排序。hash join需要足够的内存在T1(1000行)上构建一个hash表,而merge join 可能需要足够的内存来排序T3(10万行)。可见,hash join需要了更少的内存。

 

[b]Hints


虽然我们建议尽量少用hint,除非特别需要的情况下。如果你想要通过实验来查看不同的连接顺序和连接类型是如何影响执行计划和性能的,你可以使用hint来强制SQL Server来生成不同的执行计划。这些hint都在BOL里面有记录。需要注意的是不可能强迫所有的执行计划,而且使用错误的hint,可能导致SQL Server没法找到一个合法的执行计划,并产生如下错误:


Msg 8622, Level 16, State 1, Line 1
Query processor could not produce a query plan because of the hints defined in this query. Resubmit the query without specifying any hints and without using SET FORCEPLAN.

 

你可以使用查询hint来告诉SQL Server使用一个特殊的连接类型或者强迫连接的顺序。查询hint一般是出现在OPTION子句后且在每个语句的最后面。前面的例子中,我们已经使用了一些hint了:LOOP JOIN, MERGE JOIN , HASH JOIN , FORCE ORDER。前3个hint告诉优化器只能使用特定的连接类型。如果你指定了两个连接hint,优化器将会只使用其中的一个连接类型,这其实也是提供了一种不使用某个特定连接类型的方式(不在 OPTION 后面指定)。而FORCE ORDER告诉优化器根据FROM子句后面的顺序来连接两个表。

 

你也可以在FROM子句后面使用连接hint,来强制连接的类型和顺序。这些hint的功能是非常强大的。你可以为每个连接指定确定的连接类型。通过使用括号,你可以强制任何连接顺序。假设有下面这样一个例子(纯属假设),下面是使用hint来强制一棵bushy treee使用所有的3中连接类型。

select *
 
from (T1 inner merge join T2 on T1.a = T2.a)
 
    inner hash join
 
     (T3 inner loop join T4 on T3.a = T4.a)
 
    on T1.b = T3.b

 

接下来

后面,我们可能还会介绍关于连接的一些高级话题。

除非注明,本站文章均为原创或编译,转载请注明: 文章来自 sqlpub.net

5 个解决方案

#1


应该放在一个帖子里,才好啊
或者在各个帖子里加上 其他关联帖子的链接

#2


#3


前排占位,坐等版主来推荐

#4


引用 1 楼 sz_haitao 的回复:
应该放在一个帖子里,才好啊
或者在各个帖子里加上 其他关联帖子的链接


前面几篇忘加链接了,这篇的前面已经有链接啦。

#5



推荐阅读
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • Python爬虫中使用正则表达式的方法和注意事项
    本文介绍了在Python爬虫中使用正则表达式的方法和注意事项。首先解释了爬虫的四个主要步骤,并强调了正则表达式在数据处理中的重要性。然后详细介绍了正则表达式的概念和用法,包括检索、替换和过滤文本的功能。同时提到了re模块是Python内置的用于处理正则表达式的模块,并给出了使用正则表达式时需要注意的特殊字符转义和原始字符串的用法。通过本文的学习,读者可以掌握在Python爬虫中使用正则表达式的技巧和方法。 ... [详细]
  • Java程序设计第4周学习总结及注释应用的开发笔记
    本文由编程笔记#小编为大家整理,主要介绍了201521123087《Java程序设计》第4周学习总结相关的知识,包括注释的应用和使用类的注释与方法的注释进行注释的方法,并在Eclipse中查看。摘要内容大约为150字,提供了一定的参考价值。 ... [详细]
  • Whatsthedifferencebetweento_aandto_ary?to_a和to_ary有什么区别? ... [详细]
  • GreenDAO快速入门
    前言之前在自己做项目的时候,用到了GreenDAO数据库,其实对于数据库辅助工具库从OrmLite,到litePal再到GreenDAO,总是在不停的切换,但是没有真正去了解他们的 ... [详细]
  • HashMap的相关问题及其底层数据结构和操作流程
    本文介绍了关于HashMap的相关问题,包括其底层数据结构、JDK1.7和JDK1.8的差异、红黑树的使用、扩容和树化的条件、退化为链表的情况、索引的计算方法、hashcode和hash()方法的作用、数组容量的选择、Put方法的流程以及并发问题下的操作。文章还提到了扩容死链和数据错乱的问题,并探讨了key的设计要求。对于对Java面试中的HashMap问题感兴趣的读者,本文将为您提供一些有用的技术和经验。 ... [详细]
  • 本文整理了315道Python基础题目及答案,帮助读者检验学习成果。文章介绍了学习Python的途径、Python与其他编程语言的对比、解释型和编译型编程语言的简述、Python解释器的种类和特点、位和字节的关系、以及至少5个PEP8规范。对于想要检验自己学习成果的读者,这些题目将是一个不错的选择。请注意,答案在视频中,本文不提供答案。 ... [详细]
  • SoIhavealoopthatrunsperfectforeventsandonlyshowsfutureposts.TheissueisthatIwould ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • 合并列值-合并为一列问题需求:createtabletab(Aint,Bint,Cint)inserttabselect1,2,3unionallsel ... [详细]
  • HashMap的扩容知识详解
    本文详细介绍了HashMap的扩容知识,包括扩容的概述、扩容条件以及1.7版本中的扩容方法。通过学习本文,读者可以全面了解HashMap的扩容机制,提升对HashMap的理解和应用能力。 ... [详细]
  • 点击上方“新机器视觉”,选择加”星标”或“置顶”重磅干货,第一时间送达很早就想总结一下前段时间学习HALCON的心得,但由于其他的事情总是抽不出时间。去年有过一段时间的集中学习,做 ... [详细]
author-avatar
QingGang
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有