在查看sql执行计划时,我们会发现表的连接方式有多种,本文对表的连接方式进行介绍以便更好看懂执行计划和理解sql执行原理。

一、连接方式:

嵌套循环(Nested Loops (NL))

(散列)哈希连接(Hash Join (HJ))

(归并)排序合并连接(Sort Merge Join (SMJ) )

二、连接说明:

1.Oracle一次只能连接两个表。不管查询中有多少个表,Oracle在连接中一次仅能操作两张表。

2.当执行多个表的连接时,优化器从一个表开始,将它与另一个表连接;然后将中间结果与下一个表连接,以此类推,直到处理完所有表为止。

三、表连接详解:

1. NESTED LOOP

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

可用ordered提示来改变CBO默认的驱动表,可用USE_NL(table_name1 table_name2)提示来强制使用nested loop。

要点如下:
  1)对于被连接的数据子集较小的情况,嵌套循环连接是个较好的选择
  2)使用USE_NL(table_name1 table_name2)可是强制CBO 执行嵌套循环连接
  3)Nested loop一般用在连接的表中有索引,并且索引选择性较好的时候
  4)OIN的顺序很重要,驱动表的记录集一定要小,返回结果集的响应时间是最快的。
  5)Nested loops 工作方式是从一张表中读取数据,访问另一张表(通常是索引)来做匹配,nested loops适用的场合是当一个关联表比较小的时候,效率会更高。

例子如下:

SQL> create table t as select * from user_tables;

表已创建。

SQL> create index index_t on t(table_name);

索引已创建。

SQL> create table t1  as  select * from user_tables where table_name like '%ACCESS%';

表已创建。

SQL> create index index_t1 on t1(table_name);

索引已创建。

SQL> begin
  2  dbms_stats.gather_table_stats(ownname =>'TEST' ,tabname =>'T');
  3  end;
  4  /

PL/SQL 过程已成功完成。

SQL> begin
  2   dbms_stats.gather_table_stats(ownname =>'TEST' ,tabname =>'T');
  3  end;
  4  /

由于t1表记录很小作驱动表且t表的建有索引,适合NL,执行计划如下:

SQL> set wrap off;
SQL> set autotrace traceonly;

SQL> select a.table_name,b.table_name from t a,t1 b
  2  where a.table_name = b.table_name;

已选择8行。

执行计划
----------------------------------------------------------
Plan hash value: 3579965632

--------------------------------------------------------------------------------

| Id  | Operation             | Name     | Rows  | Bytes | Cost (%CPU)| Time
--------------------------------------------------------------------------------

|   0 | SELECT STATEMENT      |          |     8 |   280 |     4   (0)| 00:00:01

|   1 |  NESTED LOOPS         |          |     8 |   280 |     4   (0)| 00:00:01

|   2 |   INDEX FAST FULL SCAN| INDEX_T  |  1921 | 34578 |     4   (0)| 00:00:01

|*  3 |   INDEX RANGE SCAN    | INDEX_T1 |     1 |    17 |     0   (0)| 00:00:01

--------------------------------------------------------------------------------

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

3 - access("A"."TABLE_NAME"="B"."TABLE_NAME")

Note
-----
   - dynamic sampling used for this statement (level=2)

统计信息
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         18  consistent gets
          0  physical reads
          0  redo size
        807  bytes sent via SQL*Net to client
        415  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          8  rows processed

SQL> select a.table_name,b.table_name from t1 a,t b
  2  where a.table_name = b.table_name;

已选择8行。

执行计划
----------------------------------------------------------
Plan hash value: 3579965632

--------------------------------------------------------------------------------

| Id  | Operation             | Name     | Rows  | Bytes | Cost (%CPU)| Time
--------------------------------------------------------------------------------

|   0 | SELECT STATEMENT      |          |     8 |   280 |     4   (0)| 00:00:01

|   1 |  NESTED LOOPS         |          |     8 |   280 |     4   (0)| 00:00:01

|   2 |   INDEX FAST FULL SCAN| INDEX_T  |  1921 | 34578 |     4   (0)| 00:00:01

|*  3 |   INDEX RANGE SCAN    | INDEX_T1 |     1 |    17 |     0   (0)| 00:00:01

--------------------------------------------------------------------------------

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

3 - access("A"."TABLE_NAME"="B"."TABLE_NAME")

Note
-----
   - dynamic sampling used for this statement (level=2)

统计信息
----------------------------------------------------------
          4  recursive calls
          0  db block gets
         23  consistent gets
          0  physical reads
          0  redo size
        807  bytes sent via SQL*Net to client
        415  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          8  rows processed

------------------------------------------------------

假定我们利用提示改变的表的连接顺序

ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名,FROM子句中写在最后的表(基础表 driving table)将被最先处理,在FROM子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表。如果有3个以上的表连接查询, 那就需要选择交叉表(intersection table)作为基础表, 交叉表是指那个被其他表所引用的表.

SQL> select /*+ordered */ a.table_name,b.table_name from t1 a,t b
  2  where a.table_name=b.table_name;

已选择8行。

执行计划
----------------------------------------------------------
Plan hash value: 1123105028

------------------------------------------------------------------------------

| Id  | Operation             | Name     | Rows  | Bytes | Cost (%CPU)| Time
------------------------------------------------------------------------------

|   0 | SELECT STATEMENT      |          |     8 |   280 |     6  (17)| 00:00:

|*  1 |  HASH JOIN            |          |     8 |   280 |     6  (17)| 00:00:

|   2 |   INDEX FULL SCAN     | INDEX_T1 |     8 |   136 |     1   (0)| 00:00:

|   3 |   INDEX FAST FULL SCAN| INDEX_T  |  1921 | 34578 |     4   (0)| 00:00:

------------------------------------------------------------------------------

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

1 - access("A"."TABLE_NAME"="B"."TABLE_NAME")

Note
-----
   - dynamic sampling used for this statement (level=2)

统计信息
----------------------------------------------------------
          4  recursive calls
          0  db block gets
         20  consistent gets
          0  physical reads
          0  redo size
        807  bytes sent via SQL*Net to client
        415  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          8  rows processed

2. HASH JOIN

hash join是CBO 做大数据集连接时的常用方式。优化器扫描小表(数据源),利用连接键(也就是根据连接字段计算hash 值)在内存中建立hash表,然后扫描大表,每读到一条记录就探测hash表一次,找出与hash表匹配的行。

当小表可以全部放入内存中,其成本接近全表扫描两个表的成本之和。如果表很大不能完全放入内存,这时优化器会将它分割成若干不同的分区,不能放入内存的部分就把该分区写入磁盘的临时段,此时要有较大的临时段从而尽量提高I/O 的性能。临时段中的分区都需要换进内存做hash join。这时候成本接近于全表扫描小表+分区数*全表扫描大表的代价和。

至于两个表都进行分区,其好处是可以使用parallel query,就是多个进程同时对不同的分区进行join,然后再合并。但是复杂。

使用hash join时,HASH_AREA_SIZE初始化参数必须足够的大,如果是9i,Oracle建议使用SQL工作区自动管理,设置WORKAREA_SIZE_POLICY 为AUTO,然后调整PGA_AGGREGATE_TARGET即可。

以下条件下hash join可能有优势:
   1)两个巨大的表之间的连接。
   2)在一个巨大的表和一个小表之间的连接。

要点如下:
   1)散列连接是CBO 做大数据集连接时的常用方式.
   2)也可以用USE_HASH(table_name1 table_name2)提示来强制使用散列连接
   3)Hash join在两个表的数据量差别很大的时候.
   4)Hash join的工作方式是将一个表(通常是小一点的那个表)做hash运算并存储到hash列表中,从另一个表中抽取记录,做hash运算,到hash 列表中找到相应的值,做匹配。

可用ordered提示来改变CBO默认的驱动表,可用USE_HASH(table_name1 table_name2)提示来强制使用hash join。

例子:

SQL> set wrap off;
SQL> drop table tt purge;

表已删除。

SQL> drop table tt1 purge;

表已删除。

SQL> create table tt as select * from user_tables;

表已创建。

SQL> create table tt1 as select * from user_tables where table_name not like '%ACCESS%';

表已创建。

SQL> set autotrace traceony;

SQL> select a.table_name,b.table_name from tt a,tt1 b
  2  where a.table_name = b.table_name;

已选择1913行。

执行计划
----------------------------------------------------------
Plan hash value: 2918007165

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |  1853 | 63002 |    40   (3)| 00:00:01 |
|*  1 |  HASH JOIN         |      |  1853 | 63002 |    40   (3)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| TT1  |  1853 | 31501 |    19   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| TT   |  1939 | 32963 |    20   (0)| 00:00:01 |
---------------------------------------------------------------------------

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

1 - access("A"."TABLE_NAME"="B"."TABLE_NAME")

Note
-----
   - dynamic sampling used for this statement (level=2)

统计信息
----------------------------------------------------------
         16  recursive calls
          0  db block gets
        379  consistent gets
        117  physical reads
          0  redo size
      84524  bytes sent via SQL*Net to client
       1813  bytes received via SQL*Net from client
        129  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       1913  rows processed

3. SORT MERGE JOIN

a)对连接的每个表做table access full;
 b)对table access full的结果进行排序;
 c)进行merge join对排序结果进行合并。

sort merge join性能开销几乎都在前两步。一般是在没有索引的情况下,9i开始已经很少出现,因为其排序成本高,大多为hash join替代。
通常情况下hash join的效果都比sort merge join要好,但是,如果行源已经被排过序,在执行sort merge join时不需要再排序,这时sort merge join的性能会优于hash join。
当全表扫描比“索引范围扫描后再通过rowid进行表访问”更可取的情况下,sort merge join会比nested loops性能更佳。

要点如下:
  1)使用USE_MERGE(table_name1 table_name2)来强制使用排序合并连接.
  2)Sort Merge join 用在没有索引,并且数据已经排序的情况.
  3)连接步骤:将两个表排序,然后将两个表合并。
  4)通常情况下,只有在以下情况发生时,才会使用此种JOIN方式:
     a)RBO模式
     b)不等价关联(>,<,>=,<=,<>)
     c)bHASH_JOIN_ENABLED=false
     d)数据源已排序
     e)Merge Join 是先将关联表的关联列各自做排序,然后从各自的排序表中抽取数据,到另一个排序表中做匹配,因为merge join需要做更多的排序,所以消耗的资源更多。

f) like ,not like
        通常来讲,能够使用merge join的地方,hash join都可以发挥更好的性能

可用USE_MERGE(table_name1 table_name2)提示强制使用sort merge join。

例子:

SQL> drop table ttt purge;

表已删除。

SQL> drop table ttt1 purge;

表已删除。

SQL> create table ttt as select *  from user_tables order by table_name desc;

表已创建。

SQL> create table ttt1 as select * from user_tables  where table_name  like '%ACCESS%' order by table_name desc ;

表已创建。

当我们执行等值连接时此时为hash join:

SQL> select a.table_name,b.table_name from ttt a,ttt1 b
  2  where a.table_name=b.table_name;

已选择1913行。

执行计划
----------------------------------------------------------
Plan hash value: 1061339121

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |  1798 | 61132 |    40   (3)| 00:00:01 |
|*  1 |  HASH JOIN         |      |  1798 | 61132 |    40   (3)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| TTT  |  1798 | 30566 |    20   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| TTT1 |  1944 | 33048 |    19   (0)| 00:00:01 |
---------------------------------------------------------------------------

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

1 - access("A"."TABLE_NAME"="B"."TABLE_NAME")

Note
-----
   - dynamic sampling used for this statement (level=2)

统计信息
----------------------------------------------------------
         16  recursive calls
          0  db block gets
        377  consistent gets
        117  physical reads
          0  redo size
      84520  bytes sent via SQL*Net to client
       1813  bytes received via SQL*Net from client
        129  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       1913  rows processed

当我们在等值连接的基础上加上限制条件>,就变sort merge join

SQL> select a.table_name,b.table_name,b.tablespace_name from ttt a,ttt1 b
  2   where a.table_name > b.table_name;

已选择10581行。

执行计划
----------------------------------------------------------
Plan hash value: 200774751

----------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |   768 | 39936 |    25   (8)| 00:00:01 |
|   1 |  MERGE JOIN         |      |   768 | 39936 |    25   (8)| 00:00:01 |
|   2 |   SORT JOIN         |      |     8 |   272 |     4  (25)| 00:00:01 |
|   3 |    TABLE ACCESS FULL| TTT1 |     8 |   272 |     3   (0)| 00:00:01 |
|*  4 |   SORT JOIN         |      |  1921 | 34578 |    21   (5)| 00:00:01 |
|   5 |    TABLE ACCESS FULL| TTT  |  1921 | 34578 |    20   (0)| 00:00:01 |
----------------------------------------------------------------------------

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

4 - access("A"."TABLE_NAME">"B"."TABLE_NAME")
       filter("A"."TABLE_NAME">"B"."TABLE_NAME")

Note
-----
   - dynamic sampling used for this statement (level=2)

统计信息
----------------------------------------------------------
         10  recursive calls
          0  db block gets
         74  consistent gets
          1  physical reads
          0  redo size
     321209  bytes sent via SQL*Net to client
       8171  bytes received via SQL*Net from client
        707  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      10581  rows processed

SQL> select a.table_name,b.table_name,b.tablespace_name from ttt a,ttt1 b
  2   where a.table_name > b.table_name;

已选择10581行。

总结

类别

NESTED LOOP

SORT MERGE JOIN

HASH JOIN

优化器提示

USE_NL

USE_MERGE

USE_HASH

使用的条件

任何连接

主要用于不等价连接,如<、 <=、 >、 >=;但是不包括 <>

仅用于等价连接

相关资源

CPU、磁盘I/O

内存、临时空间

内存、临时空间

特点

当有高选择性索引或进行限制性搜索时效率比较高,能够快速返回第一次的搜索结果。

当缺乏索引或者索引条件模糊时,排序合并连接比嵌套循环有效。

当缺乏索引或者索引条件模糊时,哈希连接连接比嵌套循环有效。通常比排序合并连接快。在数据仓库环境下,如果表的纪录数多,效率高。

缺点

当索引丢失或者查询条件限制不够时,效率很低;当表的纪录数多时,效率低。

所有的表都需要排序。它为最优化的吞吐量而设计,并且在结果没有全部找到前不返回数据。

为建立哈希表,需要大量内存。第一次的结果返回较慢。

Oracle 表三种连接方式(sql优化)的更多相关文章

  1. oracle的三种连接方式

    1.通过sid jdbc:oracle:thin:@host:port:SID Example: jdbc:oracle:thin:@localhost:1521:sid_test 2.通过servi ...

  2. oracle Hash Join及三种连接方式

    在Oracle中,确定连接操作类型是执行计划生成的重要方面.各种连接操作类型代表着不同的连接操作算法,不同的连接操作类型也适应于不同的数据量和数据分布情况. 无论是Nest Loop Join(嵌套循 ...

  3. Java连接Oracle数据库的三种连接方式

    背景: 这两天在学习Oracle数据库,这里就总结下自己上课所学的知识,同时记录下来,方便整理当天所学下的知识,也同时方便日后自己查询. SQL语句的话,这里我就不多讲了,感觉和其他的数据库(MySQ ...

  4. mysql的三种连接方式

    SQL的三种连接方式分为:左外连接.右外连接.内连接,专业术语分别为:LEFT JOIN.RIGHT JOING.INNER JOIN 内连接INNER JOIN:使用比较运算符来根据指定的连接的每个 ...

  5. linux学习之centos(二):虚拟网络三种连接方式和SecureCRT的使用

    ---操作环境--- 虚拟机版本:VMware Workstation_10.0.3 Linux系统版本:CentOS_6.5(64位) 物理机系统版本:win10  一.虚拟网络三种连接方式 当在V ...

  6. Linux基石【第二篇】虚拟网络三种连接方式(转载)

    在虚拟机上安装完Centos系统后,开始配置静态IP,以方便在本宿主机上可以访问虚拟机,在曲折的配置中,了解到虚拟机还有三种连接方式:Bridged,NAT和Host-only,于是,我又一轮新的各种 ...

  7. 2019年6月14日 Web框架之Django_07 进阶操作(MTV与MVC、多对多表三种创建方式、前后端传输数据编码格式contentType、ajax、自定义分页器)

    摘要 MTV与MVC 多对多表三种创建方式 ajax ,前后端传输数据编码格式contentType 批量插入数据和自定义分页器 一.MVC与MTV MVC(Model View Controller ...

  8. [转]Apache HTTP Server 与 Tomcat 的三种连接方式介绍

    首先我们先介绍一下为什么要让 Apache 与 Tomcat 之间进行连接.事实上 Tomcat 本身已经提供了 HTTP 服务,该服务默认的端口是 8080,装好 tomcat 后通过 8080 端 ...

  9. mysql三种连接方式

    sql四种连接方式demo: 表a 表b a.id与b.parent_id有关系 1.内连接:SELECT a.*,b.* from a INNER JOIN b ON a.id=b.parent_i ...

随机推荐

  1. Linux学习之chage命令

    功能:修改帐号和密码的有效期限用法:chage[-l][-m mindays][-M maxdays][-I inactive][-E expiredate][-W warndays][-d last ...

  2. 机器学习算法实现(R&Python code)

    Machine Learning Algorithms Machine Learning Algorithms (Python and R) 明天考试,今天就来简单写写机器学习的算法 Types Su ...

  3. composer api 参考

    composer note 简介 composer 是php的依赖管理工具.可以声明项目所依赖的库,composer会帮我们安装上 composer 默认基于项目来管理和安装库(包),将依赖的库安装到 ...

  4. openStack deep dive,Retake Policy

    Method of Payment: visa MasterCard American Express Discover

  5. vb6.0 倒计时

    Dim t Dim start As Boolean Private Sub Command1_Click() If start = False Then t = Val(Text1) * 3600 ...

  6. HDU1506 ( Largest Rectangle in a Histogram ) [dp]

    近期情绪太不稳定了.可能是由于在找实习这个过程碰壁了吧.第一次面试就跪了,可能是我面的是一个新公司,制度不完好,我感觉整个面试过程全然不沾编程,我面试的还是软件开发-后来我同学面试的时候.说是有一道数 ...

  7. 在Sharepoint中批量删除大量条目

    在Sharepoint开发中可能需要一次删除成百上千条记录,这时候如果轮询SPList.Items并直接调用该对象的删除方法来删除的话性能极差,会叫你崩溃. 下面介绍一个快速删除大量数据的方法: us ...

  8. button 变成圆

      btn.layer.cornerRdius = width/2.0;btn.layer.maskToBounds = width/2.0:   

  9. 解决No Hibernate Session bound to thread, and configuration does not allow creat。。。

    applicationContext.xml配置 <?xml version="1.0" encoding="UTF-8"?> <beans ...

  10. POJ 2689 Prime Distance(素数筛选)

    题目链接:http://poj.org/problem?id=2689 题意:给出一个区间[L, R],找出区间内相连的,距离最近和距离最远的两个素数对.其中(1<=L<R<=2,1 ...