Sort merge join、Nested loops、Hash join(三种连接类型)

目前为止,典型的连接类型有3种:

Sort merge join(SMJ排序-合并连接):

首先生产driving table需要的数据,然后对这些数据按照连接操作关联列进行排序;然后生产probed table需要的数据,然后对这些数据按照与driving table对应的连接操作列进行排序;最后两边已经排序的行被放在一起执行合并操作。排序是一个费时、费资源的操作,特别对于大表。所以smj通常不是一个特别有效的连接方法,但是如果driving table和probed table都已经预先排序,则这种连接方法的效率也比较高。

Nested loops(NL嵌套循环):

连接过程就是将driving table和probed table进行一次嵌套循环的过程。就是用driving table的每一行去匹配probed table 的所有行。Nested loops可以先返回已经连接的行,而不必等待所有的连接操作处理完成才返回数据,这可以实现快速的响应时间。

Hash join(哈希连接):

较小的row source被用来构建hash table与bitmap,第二个row source用来被hashed,并与第一个row source生产的hash table进行匹配。以便进行进一步的连接。当被构建的hash table与bitmap能被容纳在内存中时,这种连接方式的效率极高。但需要设置合适的hash_area_size参数且只能用于等值连接中。

Cartesian product(笛卡尔积):表的每一行依次与另外一表的所有行匹配。

_______________________________________________________
实验:
SQL> create table segs as select * from dba_segments where owner=‘SYS‘;

Table created.

SQL> create table objts as select * from dba_objects where owner=‘SYS‘;

Table created.

SQL> select count(*) from segs;

COUNT(*)
----------
      2355
SQL> select count(*) from objts;

COUNT(*)
----------
     30967
SQL>  create index idx_segs_name on segs(segment_name);

Index created.

SQL>  create index idx_objts_name on objts(object_name);

Index created.

SQL> exec dbms_stats.gather_table_stats(user,‘SEGS‘,cascade => true);

PL/SQL procedure successfully completed.

SQL> exec dbms_stats.gather_table_stats(user,‘OBJTS‘,cascade => true);

PL/SQL procedure successfully completed.

作用:DBMS_STATS.GATHER_TABLE_STATS统计表,列,索引的统计信息.
DBMS_STATS.GATHER_TABLE_STATS的语法如下:
DBMS_STATS.GATHER_TABLE_STATS (   ownname          VARCHAR2,     tabname          VARCHAR2,     partname         VARCHAR2,   estimate_percent NUMBER,     block_sample     BOOLEAN,   method_opt       VARCHAR2,   degree           NUMBER,   granularity      VARCHAR2,     cascade          BOOLEAN,   stattab          VARCHAR2,     statid           VARCHAR2,   statown          VARCHAR2,   no_invalidate    BOOLEAN,   force            BOOLEAN);
参数说明:
ownname:要分析表的拥有者
tabname:要分析的表名.
partname:分区的名字,只对分区表或分区索引有用.
estimate_percent:采样行的百分比,取值范围[0.000001,100],null为全部分析,不采样. 常量:DBMS_STATS.AUTO_SAMPLE_SIZE是默认值,由oracle决定最佳取采样值.
block_sapmple:是否用块采样代替行采样.
method_opt:决定histograms信息是怎样被统计的.method_opt的取值如下:
for all columns:统计所有列的histograms.
for all indexed columns:统计所有indexed列的histograms.
for all hidden columns:统计你看不到列的histograms
for columns <list> SIZE <N> | REPEAT | AUTO | SKEWONLY:统计指定列的histograms.N的取值范围[1,254]; REPEAT上次统计过的histograms;AUTO由oracle决定N的大小;SKEWONLY multiple end-points with the same value which is what we define by "there is skew in the data
degree:决定并行度.默认值为null.
granularity:Granularity of statistics to collect ,only pertinent if the table is partitioned.
cascace:是收集索引的信息.默认为falase.
stattab指定要存储统计信息的表,statid如果多个表的统计信息存储在同一个stattab中用于进行区分.statown存储统计信息表的拥有者.以上三个参数若不指定,统计信息会直接更新到数据字典.
no_invalidate: Does not invalidate the dependent cursors if set to TRUE. The procedure invalidates the dependent cursors immediately if set to FALSE.
force:即使表锁住了也收集统计信息.
例子:
execute dbms_stats.gather_table_stats(ownname => ‘owner‘,tabname => ‘table_name‘ ,estimate_percent => null ,method_opt => ‘for all indexed columns‘ ,cascade => true);
------------------------------------------------------------------------------------------------------------------------
自从Oracle8.1.5引入dbms_stats包,Experts们便推荐使用dbms_stats取代analyze。 理由如下

dbms_stats可以并行分析
dbms_stats有自动分析的功能(alter table monitor )
analyze 分析统计信息的不准确some times

1,2好理解,且第2点实际上在VLDB中是最吸引人的;3以前比较模糊,看了metalink236935.1 解释,analyze在分析Partition表的时候,有时候会计算出不准确的Global statistics .

原因是,dbms_stats会实在的去分析表全局统计信息(当指定参数);而analyze是将表分区(局部)的statistics 汇总计算成表全局statistics ,可能导致误差。

SQL> select * from segs, objts where segs.segment_name=objts.object_name;

2851 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 779051904

----------------------------------------------------------------------------
| Id  | Operation          | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |       |  2450 |   528K|   139   (1)| 00:00:02 |
|*  1 |  HASH JOIN         |       |  2450 |   528K|   139   (1)| 00:00:02 |
|   2 |   TABLE ACCESS FULL| SEGS  |  2355 |   287K|    14   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| OBJTS | 30967 |  2903K|   125   (1)| 00:00:02 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("SEGS"."SEGMENT_NAME"="OBJTS"."OBJECT_NAME")

Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
        674  consistent gets
          0  physical reads
          0  redo size
     228361  bytes sent via SQL*Net to client
       2561  bytes received via SQL*Net from client
        192  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       2851  rows processed

SQL> select/*+use_merge(segs,objts)*/*from segs, objts where segs.segment_name=objts.object_name;

2851 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 2272228973

-------------------------------------------------------------------------------------
| Id  | Operation           | Name  | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |       |  2450 |   528K|       |   822   (1)| 00:00:10 |
|   1 |  MERGE JOIN         |       |  2450 |   528K|       |   822   (1)| 00:00:10 |
|   2 |   SORT JOIN         |       |  2355 |   287K|       |    15   (7)| 00:00:01 |
|   3 |    TABLE ACCESS FULL| SEGS  |  2355 |   287K|       |    14   (0)| 00:00:01 |
|*  4 |   SORT JOIN         |       | 30967 |  2903K|  8136K|   807   (1)| 00:00:10 |
|   5 |    TABLE ACCESS FULL| OBJTS | 30967 |  2903K|       |   125   (1)| 00:00:02 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

4 - access("SEGS"."SEGMENT_NAME"="OBJTS"."OBJECT_NAME")
       filter("SEGS"."SEGMENT_NAME"="OBJTS"."OBJECT_NAME")

Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
        487  consistent gets
          0  physical reads
          0  redo size
     248233  bytes sent via SQL*Net to client
       2561  bytes received via SQL*Net from client
        192  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
       2851  rows processed

SQL> select/*+use_nl(segs,objts)*/*from segs, objts where segs.segment_name=objts.object_name;

2851 rows selected.

Execution Plan
----------------------------------------------------------
Plan hash value: 2045044449

-----------------------------------------------------------------------------------------------
| Id  | Operation                    | Name           | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                |  2450 |   528K|  4725   (1)| 00:00:57 |
|   1 |  NESTED LOOPS                |                |       |       |            |          |
|   2 |   NESTED LOOPS               |                |  2450 |   528K|  4725   (1)| 00:00:57 |
|   3 |    TABLE ACCESS FULL         | SEGS           |  2355 |   287K|    14   (0)| 00:00:01 |
|*  4 |    INDEX RANGE SCAN          | IDX_OBJTS_NAME |     1 |       |     1   (0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID| OBJTS          |     1 |    96 |     2   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

4 - access("SEGS"."SEGMENT_NAME"="OBJTS"."OBJECT_NAME")

Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
       3473  consistent gets
          0  physical reads
          0  redo size
     227906  bytes sent via SQL*Net to client
       2561  bytes received via SQL*Net from client
        192  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       2851  rows processed

三种连接方式,SQL数据量、语句相同,最后获取不同的成本消耗。可以看出,当数据量达到万级之后,Nest Loop Join的随机读会急剧增加,带来的CPU成本和总执行时间成本也会大大增加。

而使用Merge Sort Join带来的块读是相对较少,但是付出的CPU成本和执行时间也是不可忽视的。将数据集合排序映射到内存中(可能要利用Temp Tablespace),需要消耗很大的CPU和内存资源(排序段)。

总体来说,Hash Join在这个SQL中还是能带来很好的综合性能的。只有块读稍大,其他指标都是可以接受的最好值。

下面我们介绍与Hash Join相关的一些系统参数,和Hash Join进行的三种操作模式。不同的系统参数,可能会给CBO成本运算带来影响。不同的操作模式,帮助我们理解PGA中的hash_area大小是如何影响到Hash Join操作的性能。

3、Hash Join相关参数

Hash Join是CBO优化器才能生成的执行计划操作,如果是选择了RBO就不能生成包括Hash Join的执行计划。此外,与Hash Join相关的Oracle参数还包括下面几个:

ü       Hash_Join_Enable

该参数是控制CBO启用Hash Join的开关。如果设置为True,则表示CBO可以使用Hash Join连接方式,否则就不可以使用。在目前的版本中,该参数已经演化为一个隐含参数,名称为“_hash_join_enable”。

SQL> col name for a20;
SQL> col value for a10;
SQL> col DESCRIB for a30;
SQL> SELECT x.ksppinm NAME, y.ksppstvl VALUE, x.ksppdesc describ
  2  FROM SYS.x$ksppi x, SYS.x$ksppcv y
  3  WHERE x.inst_id = USERENV (‘Instance‘)
  4  AND y.inst_id = USERENV (‘Instance‘)
  5  AND x.indx = y.indx
  6  AND x.ksppinm LIKE ‘%hash_join_enable%‘;

NAME                 VALUE      DESCRIB
-------------------- ---------- ------------------------------
_hash_join_enabled   TRUE       enable/disable hash join

ü       Hash_Area_Size

Hash Join操作是依赖独立的私有空间,我们称之为Hash_Area。Hash Area在Join过程中的作用就是将连接小表尽可能的缓存在Hash Area中,供进行Hash匹配和Bucket内部精确匹配。Hash Area是贮存在PGA中,属于会话session独立的一块空间。如果Hash Area较小,不足以存放小表全部数据,就会引起Temp表空间的使用,进而影响Hash Join性能。

4、连接三模式

Hash Join比较Merge Sort Join一个比较优势的地方,就是对PGA空间的有限使用上。但是,使用PGA毕竟是一种风险操作。因为Hash Area同Sort Area一样,在小表不能完全装入系统时,会调用Temp表空间的硬盘空间。这样,就会引起一些问题。

下面关于三种模式的阐述,借鉴八神前辈的《Oracle Hash Join》(http://www.alidba.net/index.php/archives/440)。特此表示感谢。

针对不同的状态,Oracle分别有不同的模式对应。

Optimal模式

这是我们进行Hash Join的最理想情况。驱动表(小表)生成的Hash数据集合可以完全存放在Hash Area的时候,我们称之为Optimal模式。

ü       首先找到驱动表,获取到驱动表。存放在Hash_Area中; 
ü       在Hash Area中,对驱动表进行Hash操作,形成Hash Bulket,形成对应的分区信息。针对多个Bulket,同时形成一个Bitmap列表,做到Bulket与Bitmap位的联系; 
ü       在各个Bulket中,分布着不同的数据行。如果连接列分布比较均匀,Bulket中数据也就比较均匀。如果Bulket中包括数据,对应该Bulket的Bitmap位上为1,否则为0; 
ü       找被驱动表的每一列,将连接列值进行Hash处理。匹配Bitmap位,如果Bitmap为0,表示该列值没有存在,直接抛弃。否则进入Bulket进行精确匹配;

Onepass模式

如果我们设置的PGA空间小,或者连接的小表体积就已经很大了,那么就会利用到临时表空间。具体处理,就是进行两次的Hash处理,在Bulket层面的上面建立Partition分区。

当进行Hash操作的时候,出现的情形是一部分的Partition在内存中,另一部分Partition被存放在Temp表空间上。

在进行连接匹配的时候,如果能够在Bitmap中确定到Partition在内存中,那么直接在内存中进行检索和精确匹配过程。否则从Temp表空间中将对应的Partition调取到内存中,进行匹配操作。

Multipass模式

这是一种很极端的情况,如果Hash Area小到一个Partition都装不下。当进行Hash操作后,只有半个Partition能装入到Hash Area。

这种情况下,如果一个Partition匹配没有做到,还不能够放弃操作,要将剩下一半的Partition获取到进行Hash Join匹配。也就是一个Partition要经过两次的Bitmap匹配过程。

5、结论

Hash Join是一种效率很高,CBO时代很常见的连接方式。但是,相对于其他古典算法,Hash Join的综合效率很高,特别在海量数据时代。

时间: 2024-12-26 08:53:50

Sort merge join、Nested loops、Hash join(三种连接类型)的相关文章

NESTED LOOPS &amp; HASH JOIN &amp; SORT MERGE JOIN

表连接方式及使用场合 NESTED LOOP 对于被连接的数据子集较小的情况,nested loop连接是个较好的选择.nested loop就是扫描一个表,每读到一条记录,就根据索引去另一个表里面查找,没有索引一般就不会是 nested loops.一般在nested loop中, 驱动表满足条件结果集不大,被驱动表的连接字段要有索引,这样就走nstedloop.如果驱动表返回记录太多,就不适合nested loops了.如果连接字段没有索引,则适合走hash join,因为不需要索引. 可用

Oracle Sql三种连接方式

Oracle执行计划详解 --- 一.相关的概念 Rowid的概念 Recursive Sql概念 Predicate(谓词) DRiving Table(驱动表) Probed Table(被探查表) 组合索引(concatenated index) 可选择性(selectivity) 二.oracle访问数据的存取方法 1) 全表扫描(Full Table Scans, FTS) 2) 通过ROWID的表存取(Table Access by ROWID或rowid lookup) 3)索引扫

Delphi中定义了四种布尔类型:Boolean,ByteBool,WordBool和LongBool。后面三种布尔类型是为了与其他语言兼容而引入的

bool是LongBool类型. Delphi中定义了四种布尔类型:Boolean,ByteBool,WordBool和LongBool.后面三种布尔类型是为了与其他语言兼容而引入的,一般情况下建议使用Boolean类型. 这四种类型的布尔值占用内存的数量如下: Boolean 1 Byte ByteBool 1 Byte WordBool 2 Bytes(1 Word) LongBool 4 Bytes(2 Words) 对于ByteBool,WordBool和LongBool三种类型True

固本培元之三:Convert、运算符、流程控制语句、ref/out/in三种参数类型

一.Convert类常用的类型转换方法Convert.ToInt32() 转换为整型(int)Convert.ToChar() 转换为字符型(char)Convert.ToString() 转换为字符串型(string)Convert.ToDateTime() 转换为日期型(datetime)Convert.ToDouble() 转换为双精度浮点型(double)Conert.ToSingle() 转换为单精度浮点型(float) 二.运算符算术运算符 + - * / %逻辑运算符 & | ^

java Data、String、Long三种日期类型之间的相互转换

java Data.String.Long三种日期类型之间的相互转换 // date类型转换为String类型 // formatType格式为yyyy-MM-dd HH:mm:ss//yyyy年MM月dd日 HH时mm分ss秒 // data Date类型的时间 public static String dateToString(Date data, String formatType) { return new SimpleDateFormat(formatType).format(data

xhtml三种元素类型

xhtml三种元素类型:块级元素/内联元素/可变元素 块级元素:独占一行.可自定义自己的宽度和高度.作为其他元素的容器,可容纳其他内联元素和块级元素,喻做一个盒子.内联元素:始终以行内逐个显示.不能设置宽高,宽高由内容支撑.可定义padding/margin/border/background.可变元素:根据上下文确定是块级元素还是内联元素.注:不同的元素类型之间可以通过display属性来实现转换. 举例:块级元素:div/h1/p/form/hr/ifrom/......内联元素:span/

VMware Workstation之虚拟网络的三种连接模式浅析

刚开始接触VMware Workstation做实验时常常为选择哪种网络连接模式而苦恼. 有时候老师说选择NAT,有时候建议桥接,还有时会要求仅主机. 唉,真是老师说什么就是什么. 既然不懂那就乖乖跟着老师屁股后面,老师怎么要求我们就怎么做,一点自己发挥的余地都不能有. 机械的跟着老师做了这么多实验,吃了这么多的苦楚,大家有没有想过,我们搭建实验环境为什么要选择不同的网络连接模式,为什么我们实验的其他部署都对了,但就是因为忽略了连接模式这个小差错而导致各种报错,为什么老师一直说实验前的ping通

Apache HTTP Server 与 Tomcat 的三种连接方式介绍

Apache HTTP Server 与 Tomcat 的三种连接方式介绍 整合 Apache Http Server 和 Tomcat 可以提升对静态文件的处理性能.利用 Web 服务器来做负载均衡以及容错.无缝的升级应用程序.本文介绍了三种整合 Apache 和 Tomcat 的方式. 3 评论: 刘 冬 ([email protected]), 开发工程师, 2007 年 1 月 15 日 内容 首先我们先介绍一下为什么要让 Apache 与 Tomcat 之间进行连接.事实上 Tomca

Linux三种进程类型

Linux操作系统包括三种不同类型的进程,每种进程都有自己的特点和属性. 1.  交互进程是由一个Shell启动的进程.交互进程既可以在前台运行,也可以在后台运行. 2.  批处理进程和终端没有联系,是一个进程序列. 3.  监控进程(也称系统守护进程)是Linux系统启动时运行的进程,并常驻后台.例如,httpd是著名的Apache服务器的监控进程. init是Linux系统操作中不可缺少的程序之一.所谓的init进程,它是一个由内核启动的用户级进程. 内核自行启动(已经被载入内存,开始运行,