详解SQL中几种常用的表连接方式!
导读:数据库性能优化最主要的就是SQL优化,SQL优化的关键离不开三点:表的连接方式、访问路径和执行顺序,本文重点介绍几种常见的连接方式。
多表关联查询,查询优化器的执行步骤具体如下。
1)访问路径:查询语句中涉及多个对象,可以基于成本确定每一个对象数据的检索方式,是选择全表扫描还是索引访问等。
2)连接方式:结果集之间的关联方式,主要包括嵌套循环、哈希连接、排序合并连接等。优化器对结果集之间连接方式的判断尤为重要,因为判断结果将会直接影响SQL的执行效率。
3)关联顺序:当关联对象超过2个时,首先选取两个对象关联得到的结果集,再与第三个结果集相关联。
下面我们重点介绍几种常见的连接方式。
01
嵌套循环连接
图1所示的是嵌套循环连接示意图。
图1 嵌套循环连接示意图
嵌套循环查询流程具体如下。
1)两表关联,优化器首先会确定驱动表,也称外部表(outer table),另一张则是被驱动的表,也称为内部表(inner table)。一般情况下,优化器会把数据量小的定义为驱动表,执行计划中,驱动表在上,被驱动表在下。
2)驱动表确认之后,会从其中提取一行有效数据,在被驱动表(内部表)中查找和匹配有效数据并提取。
3)将数据返回给客户端。
从以上步骤中我们可以看出,驱动表返回的行数直接影响了被驱动表的访问次数,比如,驱动表根据筛选条件最终返回了10行有效数据,每返回一条就会传值给被驱动表进行匹配,驱动表一共需要循环访问10次。示例代码如下:
SQL> SELECT /*+ USE_NL(e d) */ e.first_name, e.last_name, e.salary, d.department_nameFROM hr.employees e, hr.departments dWHERE d.department_name IN ('Marketing', 'Sales')AND e.department_id = d.department_id;SQL> select * from table(dbms_xplan.DISPLAY_CURSOR(null, null, 'ALLSTATS LAST'));SQL_ID 3nsqhdh150bx5, child number 0-------------------------------------SELECT /*+ USE_NL(e d) */ e.first_name, e.last_name, e.salary,d.department_name FROM hr.employees e, hr.departments d WHEREd.department_name IN ('Marketing', 'Sales') AND e.department_id =d.department_idPlan hash value: 2968905875-------------------------------------------------------------------------------------| Id | Operation |Name |Starts|E-Rows|A-Rows | A-Time | Buffers |-------------------------------------------------------------------------------------| 0 | SELECT STATEMENT | | 1 | | 36 |00:00:00.01 | 23 || 1 | NESTED LOOPS | | 1 | 19 | 36 |00:00:00.01 | 23 ||* 2 | TABLE ACCESS FULL|DEPARTMENTS| 1 | 2 | 2 |00:00:00.01 | 8 ||* 3 | TABLE ACCESS FULL|EMPLOYEES | 2 | 10 | 36 |00:00:00.01 | 15 |-------------------------------------------------------------------------------------
从上述示例代码中我们可以看出,DEPARTMENTS为驱动表,Starts为1,说明只访问1次,返回2行有效数据(A-Rows为实际返回的行数),EMPLOYEES为被驱动表,Starts为2,说明访问2次。
学过C++编程的同学应该记得,C++中的嵌套循环与下面的循环有些类似:
#include <stdio.h>int main (){int i, j;for(i=1; i<100; i++) {for(j=1; j <= 100; j++)if(!(i%j)) break;if(j > (i/j)) printf("%d \n", i);}return 0;}
j的循环次数取决于i的取值范围,我们可以将i看作驱动表,j看作被驱动表。
嵌套循环连接性能主要受限于以下几点。
驱动表的返回行数。
被驱动表的访问方式:如果被驱动表的连接列基数小且选择性差,会导致全表扫描的访问方式,其效率变得非常低,所以我们建议连接列存在索引,且基数大选择性高。
驱动表筛选后将返回少量数据。
被驱动表关联字段需要有索引(连接列基数较大或选择性较高)。
两表关联后将返回少量数据。
适合于OLTP系统。
Tips
如果优化器选择了错误的连接方式,那么我们可以使用提示(hint)强制执行使用嵌套循环的连接方式:“/*+ USE_NL(TABLE1,TABLE2) LEADING(TABLE1) */”,其中TABLE1和TABLE2为关联表的别名,LEADING(TABLE1)用于将TABLE1指定为驱动表。
02
哈希连接
图2所示的是哈希连接示意图。
图2 哈希连接示意图
嵌套循环连接适用于两表关联后将返回少量数据的情况,那么返回大量数据时该采用哪种连接方式呢?答案是采用哈希连接。
哈希连接的查询流程具体如下。
1)两表等值关联。
2)优化器将数据量小的表作为驱动表,在PGA的SQL 工作区域(work areas)中,将驱动表的连接列构建成一张哈希表。
3)读取大表,对连接列进行哈希运算(检查哈希表,以查找连接的行)。
4)将数据返回给客户端。
从以上步骤中我们可以看出,通过哈希值进行匹配的方式,更适用于两表等值关联。示例代码如下:
SQL> SELECT /*+ USE_HASH(o l) */o.customer_id, l.unit_price * l.quantity2 FROM oe.orders o, oe.order_items l3 WHERE l.order_id = o.order_id;SQL> select * from table(dbms_xplan.DISPLAY_CURSOR(null, null, 'ALLSTATS LAST'));SQL_ID cu980xxpu0mmq, child number 0-------------------------------------SELECT /*+ USE_HASH(o l) */o.customer_id, l.unit_price * l.quantityFROM oe.orders o, oe.order_items l WHERE l.order_id = o.order_idPlan hash value: 864676608-------------------------------------------------------------------------------------------------------------| Id | Operation |Name |Starts|E-Rows|A-Rows|A-Time |Buffers|Reads|OMem |1Mem |Used-Mem|-------------------------------------------------------------------------------------------------------------| 0 | SELECT STATEMENT | | 1 | | 665 |00:00:00.04 | 57 | 5 | | | ||* 1 | HASH JOIN | | 1 | 665 | 665 |00:00:00.04 | 57 | 5 |1888K|1888K|1531K (0)|| 2 | TABLE ACCESS FULL|ORDERS | 1 | 105 | 105 |00:00:00.04 | 6 | 5 | | | || 3 | TABLE ACCESS FULL|ORDER_ITEMS| 1 | 665 | 665 |00:00:00.01 | 51 | 0 | | | |-------------------------------------------------------------------------------------------------------------
从上述示例代码中我们可以看出,ORDERS为驱动表,Starts为1,说明访问1次,返回105行有效数据(A-Rows为实际返回的行数),ORDER_ITEMS为被驱动表,Starts也为1,说明仅访问1次。其中,OMem、1Mem为执行所需的PGA评估值,Used-Mem为实际执行时PGA中SQL工作区域消耗的内存(即发生磁盘交换的次数),当驱动表较大,PGA的SQL 工作区域无法完全容纳时,就会溢出到临时表空间产生磁盘交互,进而影响性能。
哈希连接性能主要受限于以下两点。
等值连接。
PGA SQL工作区域较小,且驱动表为大表时,容易出现性能问题。
当同时满足以下条件时,哈希连接方式将会非常有用。
两表等值关联后返回大量数据。
不同于嵌套循环连接,哈希连接被驱动表的连接字段时不需要有索引。
Tips
同样,我们也可以使用提示强制执行使用哈希连接的方式:“/*+ USE_HASH (TABLE1,TABLE2) LEADING(TABLE1) */”。
03
排序合并连接
图3所示的是排序合并连接示意图。
图3 排序合并连接示意图
哈希连接适用于两表等值关联后返回大量数据的情况,那么非等值关联返回大量数据的情况又该采用哪种连接方式呢?答案是排序合并连接。
同时满足以下条件时,排序合并连接的性能要比哈希连接得好。
两表非等值关联(>、>=、<、<=、<>)。
数据源自身有序。
不必额外执行排序操作。
排序合并连接方式中没有驱动表的概念,连接查询流程具体如下。
1)两表根据关联列各自排序。
2)在内存中进行合并处理。
从以上实现步骤中我们可以看出,由于匹配的对象是连接列各自排序后的值,因此排序合并连接方式更适用于两表非等值关联的情形,示例代码如下:
SQL> SELECT o.customer_id, l.unit_price * l.quantityFROM oe.orders o, oe.order_items lWHERE l.order_id > o.order_id;32233 rows selected..SQL> select * from table(dbms_xplan.DISPLAY_CURSOR(null, null, 'ALLSTATS LAST'));SQL_ID ajyppymnhwfyf, child number 1-------------------------------------SELECT o.customer_id, l.unit_price * l.quantity FROM oe.orders o,oe.order_items l WHERE l.order_id > o.order_idPlan hash value: 2696431709-----------------------------------------------------------------------------------------------------------| Id | Operation |Name |Starts| E-Rows | A-Rows | A-Time |Buffers|OMem |1Mem | Used-Mem |-----------------------------------------------------------------------------------------------------------| 0 | SELECT STATEMENT | | 1 | | 32233 |00:00:00.10 | 21 | | | || 1 | MERGE JOIN | | 1 | 3 4580 | 32233 |00:00:00.10 | 21 | | | || 2 | SORT JOIN | | 1 | 105 | 105 |00:00:00.01 | 4 |11264|11264|10240 (0)|| 3 | TABLE ACCESS FULL |ORDERS | 1 | 105 | 105 |00:00:00.01 | 4 | | | ||* 4 | SORT JOIN | | 105 | 665 | 32233 |00:00:00.05 | 17 |59392|59392|53248 (0)|| 5 | TABLE ACCESS FULL |ORDER_ITEMS| 1 | 665 | 665 |00:00:00.01 | 17 | | | |------------------------------------------------------------------------------------------------------------
从上述示例所示的执行计划中我们可以看出,ID=3的ORDERS表Starts为1,说明访问1次,返回105行有效数据(A-Rows为实际返回行数),ORDER_ITEMS表的Starts为1,说明也只访问1次,但ID=4的SORT JOIN表Starts为105,说明在内存中进行了105次匹配。其中,OMem、1Mem为执行排序操作所需的PGA评估值,Used-Mem为实际执行时PGA中SQL工作区域消耗的内存(即发生磁盘交换的次数)。
从以上步骤中我们可以看出,由于比较对象是两张表的连接列order_id,所以需要各自的连接列先完成排序(ID=2和ID=4),之后再进行匹配。如果此时连接列上存在索引,那么索引返回的数据就是有序的,此时不需要再进行额外的排序操作。
Tips
同样,我们也可以使用提示强制执行选择排序合并连接的方式:“/*+ USE_MERGE(TABLE1,TABLE2) */”。
04
笛卡尔连接
当一个或多个表连接没有任何连接条件时,数据库将使用笛卡儿连接。优化器将一个数据源的每一行与另一个数据源的每一行连接在一起,以创建两组数据集的笛卡儿积。示例代码如下:
SQL> SELECT o.customer_id, l.unit_price * l.quantityFROM oe.orders o, oe.order_items l;69825 rows selected.SQL> select * from table(dbms_xplan.DISPLAY_CURSOR(null, null, 'ALLSTATS LAST'));SQL_ID d3xygy88uqzny, child number 0-------------------------------------SELECT o.customer_id, l.unit_price * l.quantity FROM oe.orders o,oe.order_items lPlan hash value: 2616129901-----------------------------------------------------------------------------------------------| Id | Operation | Name |Starts | E-Rows | Buffers | OMem | 1Mem | Used-Mem |-----------------------------------------------------------------------------------------------| 0 | SELECT STATEMENT | | 1 | | 125 | | | || 1 | MERGE JOIN CARTESIAN| | 1 | 69825 | 125 | | | || 2 | TABLE ACCESS FULL |ORDERS | 1 | 105 | 108 | | | || 3 | BUFFER SORT | | 105 | 665 | 17 | 27648 | 27648 |24576 (0)|| 4 | TABLE ACCESS FULL |ORDER_ITEMS| 1 | 665 | 17 | | | |-----------------------------------------------------------------------------------------------
从以上执行计划中我们可以看出,先对表order_items进行排序,然后进行两表的笛卡儿乘积操作,由于没有过滤条件,当数据量很大的时候,返回的行数将会非常多,因此若无特殊情况,不建议使用没有任何连接条件的查询。
以上。
详解SQL中几种常用的表连接方式!相关推荐
- 详解SQL的四种连接-左外连接、右外连接、内连接、全连接
1.内联接(典型的联接运算,使用像 = 或 <> 之类的比较运算符).包括相等联接和自然联接. 内联接使用比较运算符根据每个表共有的列的值匹配两个表中的行.例如,检索 stud ...
- 详解SQL中Groupings Sets 语句的功能和底层实现逻辑
前言 SQL 中 Group By 语句大家都很熟悉, 根据指定的规则对数据进行分组 ,常常和 聚合函数 一起使用. 比如,考虑有表 dealer ,表中数据如下: 如果执行 SQL 语句 S ...
- Case When语句详解SQL中Case When的用法
SQL中case when的用法 case when类似于编程语言中的if else判断.switch case语句.该语句执行时先对条件进行判断,然后根据判断结果做出相应的操作. Case具有两种格 ...
- sql去重复操作详解SQL中distinct的用法
在表中,可能会包含重复值.这并不成问题,不过,有时您也许希望仅仅列出不同(distinct)的值.关键词 distinct用于返回唯一不同的值. 表A: 表B: 1.作用于单列 select dist ...
- 详解 Java 中 4 种 I/O 模型
同步.异步.阻塞.非阻塞都是和I/O(输入输出)有关的概念,最简单的文件读取就是I/O操作.而在文件读取这件事儿上,可以有多种方式. 本篇会先介绍一下I/O的基本概念,通过一个生活例子来分别解释下这几 ...
- 详解Yarn中三种资源调度器(FIFO Scheduler、Capacity Scheduler、Fair Scheduler)和配置自定义队列实现任务提交不同队列
前言 在前面 Yarn的基本架构和作业提交全流程 一文中提到,当ResourceManager收到客户端Client的请求之后会将该作业job添加到(默认的)容量调度器中,然后再由某一个空闲的Node ...
- 详解SQL中drop、delete和truncate的异同
第一:相同点: truncate和不带where子句的delete,以及drop 都会删除表内的数据 第二:不同点: 1. truncate和delete只删除数据不删除表的结构(定义) dr ...
- 详解SQL中的触发器
● 原因 ● 触发器 ○ 简介 ○ 分类 ○ INSERTED和DELETED ○ 优缺点 ● 语法 ○ 建立触发器 ○ 删除触发器 ○ 修改触发器 ○ 开启和禁用 ○ 提醒和保护 ● 示例 原因 今 ...
- 深度盘点:一文详解数据分析中100个常用指标和术语
大家好,有个朋友是金融行业产品经理,最近在对已有的站内用户做分层与标签分类,需要对用户进行聚类分析.一般从事数据分析行业的朋友对这类词并不陌生,但是像市场运营人员就会把这类些名词概念搞混,导致结果不准 ...
最新文章
- MySQL面试题 | 附答案解析(二十)
- 中国顶尖的技术社区们在一个群里,会聊什么…
- java linux 起多个进程_linux下tomcat启动后出现多个java进程
- win7系统启动到一半停止_win7系统启动一半死机的解决方法
- web端ios布局fixed元素软键盘唤起时fixed失效
- 段错误 php,php扩展出现段错误怎么办
- Game with string
- OPA 6 - module(Create Button Test);
- 计算机考研调剂规则,21考研调剂规则大变化,这类学生不能调剂!
- Python panads数据处理二
- 蓝桥杯 ALGO-64 算法训练 大小写判断
- 《iOS8 Swift编程指南》类书图像
- STM32或GD32驱动TM1637
- oppo,ofp格式解包工具.Unpack ofp oppo手机,ofp线刷包解包教程
- 3451. 易位构词
- java中的元音 辅音_元音辅音分类表
- css做三角形横线加小三角,CSS创建三角形(小三角)的几种方法
- 数控机床设备物联网远程控制解决方案
- Windows 11 电脑如何设置自动开机 (Windows 11 2022H2)
- 中国北斗简单原理随笔
热门文章
- iphone长截图哪个软件好_这应该是目前为止,iPhone上最好的长截图工具
- 中国国产CPU研发现状
- 将3060独显笔记本升级为高级AI工作站
- “AI芯片”通识_AI产品经理看这一篇就够了_团员分享_@书博
- 游戏王,查卡器,编号,开源,代码,OCG,程序
- 熬粥记:煮一碗红豆粥,3次才领悟煮粥大法
- 【前端CSS】offsetLeft,Left,clientLeft的区别
- DSF(Debugger Services Framework) service介绍
- 进程间通讯 --- 管道(半双工通信)
- 华为云语音识别:一句话识别API调用