查询优化器内核剖析第六篇:谈谈Join的顺序问题,纠正江湖偏方

系列文章:

查询优化器内核剖析第一篇

查询优化器内核剖析第二篇:产生候选执行计划&执行计划成本估算

查询优化器内核剖析第三篇:查询的执行与计划的缓存 & Hint提示

查询优化器内核剖析第四篇:从一个实例看执行计划

查询优化器内核剖析第五篇:进一步的了解执行计划

查询优化器内核剖析第六篇:谈谈Join的顺序问题,纠正江湖偏方

查询优化器内核剖析第七篇:执行引擎之数据访问操作---Scan

查询优化器内核剖析第八篇:执行引擎之数据访问操作---Seek与Lookup

查询优化器内核剖析第九篇:执行引擎之数据访问操作---Bookmark Lookup

查询优化器内核剖析第十篇:执行引擎之数据访问操作- Aggregations(聚合)

可以说Join的顺序是查询优化过程中最复杂的问题(当然,这里说的复杂,是对于查询优化器而言,我们很多时候没有看到,也没用怎么在意这个复杂性),因为它涉及到了为每个可选的Join的顺序计算成本。可以这样说:Join的顺序与个数直接与产生候选的执行计划的数量挂钩,从而极大的影响查询优化器的优化过程。

提到Join,我们都很熟悉了:就是把来自两个表的基于一些公共的信息进行联合的操作。Join的操作每次都只能在两个表之间进行,因此,如果一个查询要Join的表有N个,那么,就需要进行(N-1)次的Join操作,即:先第1个表和第2个表Join,然后Join的结果在和第3个表进行Join,以此类推。

关于Join,查询优化器会根据以下两点做出重要的决定:

  1. Join顺序的选择
  2. Join算法的选择

在这部分,我们重要谈谈Join的顺序的问题,相信看完之后,一定会让大家对Join有一个全新的认识,至于Join算法,因为这涉及到了执行引擎的部分,我们会在后面的文章中讲述。

之前提过,表的Join的顺序决定了一个查询的性能与成本。对已一个查询而言,可以产生很多的候选执行计划,虽然这些执行计划返回的结果一样,但是不同的Join产生的执行计划的成本确实相差很大的。

因为Join操作有着交换律结合律的特性,所以,即使一个再简单的查询,也会因为Join的顺序不同而产生很多不同的执行计划,而执行计划的数量也会随着Join表的个数的增加而呈指数倍的增加。而查询优化器的任务就是在这些众多的执行计划中选择一个比较优的Join顺序的执行计划。

为了便于理解,我们先来看看几个概念,然后深入的讲述一些例子。

首先看看“交换律”。这一个数学概念,因为SQL  Server是关系型数据,其理论基础就是关系集合论,而现在的数学都是建立在集合的基础上的,因此SQL Server在设计的时候,也引入了很多的数学的理论。Join操作的就体现了交换律:A  Join B等于B Join A。

       交换律也定义了哪一个表被先访问,例如在一个Nest Loops Join的操作中,首先被访问的表就被称之为“外部表”,而第二个表就称之为“内部表”。在Hash Join中,首先被访问的表称之为“内部构建表”,第二个表为“探针输入”。定义哪一个表为“外部表”或者为“内部构建表”、“探针输入”对性能的影响都是巨大的。

Join操作的另外一个特性就是:结合律,即:(A Join B) Join C等于A Join (B join C),这个概念也不难理解。下面,我们就通过一个实例来看看。

我们AdventureWorks示例数据库为例子,我们为下面的这个查询产生实际的执行计划:

我们来看看执行计划,如下:

可以看出:

  1. 我们在代码中Join的顺序是:(Contact  Join Individual)Join Customer,但是在执行计划中的Join顺序却是:(Sales.Customer JOIN Sales.Individual) JOIN Person.Contact。

从这里就可以知道:我们在代码中的Join顺序,不见得就是最后执行计划的顺序,也证明了网络上面流行的一些偏方是错误的:通过改变Join的顺序来提升性能。因为查询优化器相当的智能,对于Join的顺序,它有着自己的决定和考虑。

2.查询优化器会根据很多的信息来决定到底逻辑的Join操作最后又哪一种物理Join操作来执行。在例子的代码中,我们的Join都是逻辑的Inner Join,但是最后查询优化器会选择合适的物理Join操作,例如对于(Sales.Customer JOIN Sales.Individual),选择的就是Merge Join,因为这个两个表是基于聚集索引进行连接,并且数据都是按照聚集索引来排序的(更多的讲述,我们后面文章会涉及)。

当然,我们也可以通过使用Hint来要求查询优化器按照我们的要求来强制的产生Join的类型和顺序,我们来看下面的一个例子:

这时候,产生的执行计划如下:

可以看出,查询优化器按照我们的要求进行了Join的连接顺序。

我们诚然可以通过使用Hint来改变查询优化器的行为,但是我们同时也要注意每个执行执行的成本。很多时候,我们使用Hint会带来很多的问题,而且查询优化器常常会选择比较好的执行方式和执行计划(在我们提供了正确的信息的情况下,例如统计信息等)。

我们之前提到过:表的Join的顺序会随着表数量的增长而呈指数倍的增加。而实际的情况是,只有很少的一些表,它们的Join的顺序的组合方式的数量会达到成千上万,组合的方式的数量主要取决于查询树的结构。而且,查询优化器也不可能为这个每一种组合都去进行成本估算,而是采用启发式的算法,根据查询树的结构和形状来减少搜索的空间。

这里可能不怎么好理解,我们稍微的讲一讲。

在之前的相关文章中介绍过:一个提交的查询语句经过一些处理之后,会以树状的结构出现在查询处理器中,而树形的结构对查询优化器来说又是至关重要的。树形的结构基本可以分为:左深度树,右深度树,多路树。

例如,对于:JOIN( JOIN( JOIN(A, B), C), D)这样的语句而言,可能产生左深度树或者右深度树,如图所示:

而对于:JOIN(JOIN(A, B), JOIN(C, D))的语句,可能产生下面的树:

根据表数量的不同,Join的顺序会产生很多的不同的树形,而这不同的树形最终会产生出不同的执行计划。

下面,我们就给出一个表数据与Join组合方式的数量关系。

表数量

左深度树的数量

多路树的数量

1

1

1

2

2

2

3

6

12

4

24

120

5

120

1,680

6

720

30,240

7

5,040

665,280

8

40,320

17,297,280

9

362,880

518,918,400

10

3,628,800

17,643,225,600

11

39,916,800

670,442,572,800

12

479,001,600

28,158,588,057,600

很吓人吧!左深度树的数量基于是n!个(n的拉斐尔函数,n的阶乘,其中n是表的个数)。对于多路树,数量则是(2n–2)!/(n–1)!

另外,需要记住的就是:上面还只是产生的执行计划的可能数量而言,对于其中的每一个执行计划,查询优化器还要去选择不同的物理操作和数据访问方法(例如,是进行Table Scan还是Index Scan还是Index Seek等),而这些再次乘以之前的数量,查询优化器的工作量可想而知。

最后一个问题是:查询优化器会对上面的所有的组合方式的成本进行估算吗?

答案是:不会!查询优化器会综合考虑很多的因素来决定。

我们后面文章会讲述,今天到这里为止!

其他文章链接

IIS负载均衡-Application Request Route详解第一篇: ARR介绍  

IIS负载均衡-Application Request Route详解第二篇:创建与配置Server Farm

 IIS负载均衡-Application Request Route详解第三篇:使用ARR进行Http请求的负载均衡(上)

IIS负载均衡-Application Request Route详解第三篇:使用ARR进行Http请求的负载均衡(下)

IIS负载均衡-Application Request Route详解第四篇:使用ARR实现三层部署架构

转载于:https://www.cnblogs.com/yanyangtian/archive/2012/04/09/2438378.html

查询优化器内核剖析第六篇:谈谈Join的顺序问题,纠正江湖偏方相关推荐

  1. 查询优化器内核剖析第四篇:从一个实例看执行计划

    查询优化器内核剖析第四篇:从一个实例看执行计划 系列文章索引: 查询优化器内核剖析第一篇 查询优化器内核剖析第二篇:产生候选执行计划&执行计划成本估算 查询优化器内核剖析第三篇:查询的执行与计 ...

  2. 查询优化器内核剖析第一篇

    查询优化器内核剖析第一篇 查询优化器内核剖析第一篇 查询优化器内核剖析第二篇:产生候选执行计划&执行计划成本估算 查询优化器内核剖析第三篇:查询的执行与计划的缓存 & Hint提示 查 ...

  3. Linux系统调用详解(实现机制分析)--linux内核剖析(六)

    本文介绍了系统调用的一些实现细节.首先分析了系统调用的意义,它们与库函数和应用程序接口(API)有怎样的关系.然后,我们考察了Linux内核如何实现系统调用,以及执行系统调用的连锁反应:陷入内核,传递 ...

  4. 如何阅读《数据库查询优化器的艺术:原理解析与SQL性能优化》

    附录B 如何阅读本书 本书是一本数据库内核相关书籍,从数据库的查询优化器入手,对数据库的查询优化引擎进行了分析和对比,对查询优化的技术做了全面的总结和剖析.从不同角度看,可能有着不同的感受:不同角色的 ...

  5. ExoPlayer播放器剖析(六)ExoPlayer同步机制分析

    关联博客 ExoPlayer播放器剖析(一)进入ExoPlayer的世界 ExoPlayer播放器剖析(二)编写exoplayer的demo ExoPlayer播放器剖析(三)流程分析-从build到 ...

  6. MVC教程第六篇:拦截器

    MVC教程第六篇:拦截器      摘要 本文将对"MVC公告发布系统"的发布公告功能添加日志功能和异常处理功能,借此来讨论ASP.NET MVC中拦截器的使用方法. 一个小难题 ...

  7. SQL Server调优系列进阶篇(查询优化器的运行方式)

    前言 前面我们的几篇文章介绍了一系列关于运算符的基础介绍,以及各个运算符的优化方式和技巧.其中涵盖:查看执行计划的方式.几种数据集常用的连接方式.联合运算符方式.并行运算符等一系列的我们常见的运算符. ...

  8. 《Linux内核剖析》(Yanlz+VR云游戏+Unity+SteamVR+云技术+5G+AI+Makefile+块设备驱动+字符设备驱动+数学协处理器+文件系统+内存管理+GDB+立钻哥哥+==)

    <Linux内核剖析> <Linux内核剖析> 版本 作者 参与者 完成日期 备注 YanlzLinux_Kernel0.12_V01_1.0 严立钻 2020.02.06 # ...

  9. Atitit sql计划任务与查询优化器--统计信息模块

    Atitit sql计划任务与查询优化器--统计信息模块 每一个统计信息的内容都包含以上三部分的内容. 我们依次来分析下,通过这三部分内容SQL Server如何了解该列数据的内容分布的. a.统计信 ...

  10. Android内核剖析

    -- Android内核剖析  柯元旦 编著 ISBN 978-7-121-14398-4   2011年9月出版 定价:79.90元 16开 616页 内容简介: 本书内容分别从基础.内核.系统.编 ...

最新文章

  1. [RHEL5企业级Linux服务攻略]--第3季 DHCP服务全攻略
  2. 2018/12/07 L1-033 出生年 Java
  3. SourceInsight 汉化
  4. 教你如何将自己的脚本代码放到服务器上运行
  5. kmeanssklearn实例_sklearn实践(一):kmeans聚类
  6. java 方法 示例_Java集合syncedSortedSet()方法与示例
  7. 利用逆矩阵解线性方程组_线性代数入门——矩阵方程简介及一类基本矩阵方程的解法...
  8. shell判断进程使用CPU时间后kill进程
  9. 趣谈新春年俗:古代过年为啥要贴门神像?
  10. 支付宝玉伯:我心目中的优秀API
  11. uiso9|uiso9_cn.exe
  12. SetTimer函数用法
  13. pxe(hanewin)安装ubuntu-七侠镇莫小贝
  14. 海量计算机入门视频教程
  15. 模式识别和机器学习的区别
  16. ad中按钮开关的符号_电工最常用电气元件实物及对应符号
  17. 【收藏】40 个学术网站,满足科研文献需求!
  18. python爬虫学习之路(二)re库的使用方法
  19. ubuntu 检测网络状态命令
  20. 红米5a手机html查看器,红米5A如何截图 红米5A手机截图方法【详细介绍】

热门文章

  1. GCN实践——可视化cora-network
  2. MatConvnet工具箱文档翻译理解一
  3. Python图数据库neo4j学习实践
  4. python实现多个excel文件两种方式合并:多表单形式合并、单表单(增加标识列来区分不同文件)形式合并
  5. python bottle 终止返回_关于python的bottle框架跨域请求报错问题的处理
  6. c++ websocket客户端_ESP32 Arduino教程:Websocket客户端
  7. 区块链 以太坊 三元组是什么 state root transaction root receipt root
  8. 请简述java的异常体系,Java的异常体系 面试题
  9. wordpress数据表说明
  10. php 代码的分离和调用及注意事项(版本、变量及cookie与session的区别,PHP包含文件函数include、include_once、require、require_once区别和总结)