一. nested loop 原理

nested loop 连接(循环嵌套连接)指的是两个表连接时, 通过两层嵌套循环来进行依次的匹配, 最后得到返回结果集的表连接方法.

假如下面的 sql 语句中表 T1 和 T2 的连接方式是循环嵌套连接, T1 是驱动表
select *
from T1, T2
where T1.id = T2.id and T1.name = 'David';
那么将上述 sql 语句翻译为伪码应该如下所示:

 for each row in (select * from T1 where name = 'David') loop
for (select * from T2 where T2.id = outer.id) loop
If match then pass the row on to the next step
If no match then discard the row
end loop
end loop

具体来说, 如果上述 sql 语句执行循环嵌套连接的话, 那么实际的执行过程应该如下所示:

(1) 首先 oracle 会根据一定的规则(根据统计信息的成本计算或者 hint 强制)决定哪个表是驱动表, 哪个表是被驱动表 (假设 T1 是驱动表)
(2) 查询驱动表 "select * from T1 where name = 'David'" 然后得到驱动结果集 Q1
(3) 遍历驱动结果集 Q1 以及被驱动表 T2, 从驱动结果集 Q1 中取出一条记录, 接着遍历 T2 并按照连接条件 T2.id = T1.id 去判断 T2 中是否存在匹配的记录,
如果能够匹配则保留, 不能匹配则忽略此行, 然后再从 Q1 中取出下一条记录, 接着遍历 T2 进行匹配, 如此下去直到取完 Q1 中的所有记录

具体来说, 如果上述 sql 语句执行循环嵌套连接的话, 那么实际的执行过程应该如下所示:
(1) 首先 oracle 会根据一定的规则(根据统计信息的成本计算或者 hint 强制)决定哪个表是驱动表, 哪个表是被驱动表 (假设 T1 是驱动表)
(2) 查询驱动表 "select * from T1 where name = 'David'" 然后得到驱动结果集 Q1
(3) 遍历驱动结果集 Q1 以及被驱动表 T2, 从驱动结果集 Q1 中取出一条记录, 接着遍历 T2 并按照连接条件 T2.id = T1.id 去判断 T2 中是否存在匹配的记录,
如果能够匹配则保留, 不能匹配则忽略此行, 然后再从 Q1 中取出下一条记录, 接着遍历 T2 进行匹配, 如此下去直到取完 Q1 中的所有记录

二. nested loop 特性

嵌套循环连接有以下特性:

(1) 通常 sql 语句中驱动表只访问一次, 被驱动表访问多次
(2) 不必等待处理完成所有行前可以先返回部分已经处理完成的数据
(3) 在限制条件以及连接条件列上建立索引, 能够提高执行效率
(4) 支持所有类型的连接 (等值连接, 非等值连接, like 等)

构造试验数据

SQL> CREATE TABLE t1 (
2 id NUMBER NOT NULL,
3 n NUMBER,
4 pad VARCHAR2(4000),
5 CONSTRAINT t1_pk PRIMARY KEY(id)
6 );
Table created. SQL> CREATE TABLE t2 (
2 id NUMBER NOT NULL,
3 t1_id NUMBER NOT NULL,
4 n NUMBER,
5 pad VARCHAR2(4000),
6 CONSTRAINT t2_pk PRIMARY KEY(id),
7 CONSTRAINT t2_t1_fk FOREIGN KEY (t1_id) REFERENCES t1
8 );
Table created. SQL> CREATE TABLE t3 (
2 id NUMBER NOT NULL,
3 t2_id NUMBER NOT NULL,
4 n NUMBER,
5 pad VARCHAR2(4000),
6 CONSTRAINT t3_pk PRIMARY KEY(id),
7 CONSTRAINT t3_t2_fk FOREIGN KEY (t2_id) REFERENCES t2
8 );
Table created. SQL> CREATE TABLE t4 (
2 id NUMBER NOT NULL,
3 t3_id NUMBER NOT NULL,
4 n NUMBER,
5 pad VARCHAR2(4000),
6 CONSTRAINT t4_pk PRIMARY KEY(id),
7 CONSTRAINT t4_t3_fk FOREIGN KEY (t3_id) REFERENCES t3
8 );
Table created. SQL> execute dbms_random.seed(0)
PL/SQL procedure successfully completed. SQL> INSERT INTO t1 SELECT rownum, rownum, dbms_random.string('a',50) FROM dual CONNECT BY level <= 10 ORDER BY dbms_random.random;
10 rows created. SQL> INSERT INTO t2 SELECT 100+rownum, t1.id, 100+rownum, t1.pad FROM t1, t1 dummy ORDER BY dbms_random.random;
100 rows created. SQL> INSERT INTO t3 SELECT 1000+rownum, t2.id, 1000+rownum, t2.pad FROM t2, t1 dummy ORDER BY dbms_random.random;
1000 rows created. SQL> INSERT INTO t4 SELECT 10000+rownum, t3.id, 10000+rownum, t3.pad FROM t3, t1 dummy ORDER BY dbms_random.random;
10000 rows created. SQL> COMMIT;
Commit complete.

使用 hint 让 sql 语句通过 nested loop 连接, 并且指定 t3 为驱动表

 SQL> select /*+ leading(t3) use_nl(t4) */ * from t3, t4
2 where t3.id = t4.t3_id and t3.n = 1100; 10 rows selected. SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last')); PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------
SQL_ID 89hnfwqakjghg, child number 0
-------------------------------------
select /*+ leading(t3) use_nl(t4) */ * from t3, t4 where t3.id = t4.t3_id and t3.n = 1100 Plan hash value: 1907878852 -------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.01 | 121 |
| 1 | NESTED LOOPS | | 1 | 10 | 10 |00:00:00.01 | 121 |
|* 2 | TABLE ACCESS FULL| T3 | 1 | 1 | 1 |00:00:00.01 | 16 |
|* 3 | TABLE ACCESS FULL| T4 | 1 | 10 | 10 |00:00:00.01 | 105 |
------------------------------------------------------------------------------------- Predicate Information (identified by operation id):
--------------------------------------------------- 2 - filter("T3"."N"=1100)
3 - filter("T3"."ID"="T4"."T3_ID")

在执行计划中我们可以看到驱动表 T3 访问一次, 因为驱动表上有谓词条件 t3.n = 1100, 通过执行谓词条件后驱动结果集的记录数为 1, 所以 T4 也只访问一次(starts 列)

使用 hint 让 sql 语句通过 nested loop 连接, 并且指定 t4 为驱动表

 SQL> select /*+ leading(t4) use_nl(t3) full(t4) full(t3) */ * from t3, t4 where t3.id = t4.t3_id and t3.n = 1100;

 SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));

 PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------
SQL_ID 0yxm1muqwrfq2, child number 0
-------------------------------------
select /*+ leading(t4) use_nl(t3) full(t4) full(t3) */ * from t3, t4
where t3.id = t4.t3_id and t3.n = 1100 Plan hash value: 3886808168 -------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.25 | 150K|
| 1 | NESTED LOOPS | | 1 | 10 | 10 |00:00:00.25 | 150K|
| 2 | TABLE ACCESS FULL| T4 | 1 | 10000 | 10000 |00:00:00.01 | 105 |
|* 3 | TABLE ACCESS FULL| T3 | 10000 | 1 | 10 |00:00:00.21 | 150K|
------------------------------------------------------------------------------------- Predicate Information (identified by operation id):
--------------------------------------------------- 3 - filter(("T3"."N"=1100 AND "T3"."ID"="T4"."T3_ID"))

在执行计划中我们可以看到驱动表 T4 访问一次, 因为驱动表上 T4 结果集的记录数为 10000, 所以 T4 访问了 10000 次, buffers 和 A-time(实际执行时间) 都比较高.

三. nested loop 优化

在 nested loop 被驱动表上的连接列上 (T4 表的 t3_id 列) 建立索引

 SQL> CREATE INDEX t4_t3_id ON t4(t3_id);

 Index created.

 SQL> select /*+ leading(t3) use_nl(t4) */ * from t3, t4 where t3.id = t4.t3_id and t3.n = 1100;

 10 rows selected.

 SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));

 PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 89hnfwqakjghg, child number 0
-------------------------------------
select /*+ leading(t3) use_nl(t4) */ * from t3, t4 where t3.id = t4.t3_id and t3.n = 1100 Plan hash value: 2039660043 ------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.01 | 29 | 1 |
| 1 | NESTED LOOPS | | 1 | | 10 |00:00:00.01 | 29 | 1 |
| 2 | NESTED LOOPS | | 1 | 10 | 10 |00:00:00.01 | 19 | 1 |
|* 3 | TABLE ACCESS FULL | T3 | 1 | 1 | 1 |00:00:00.01 | 16 | 0 |
|* 4 | INDEX RANGE SCAN | T4_T3_ID | 1 | 10 | 10 |00:00:00.01 | 3 | 1 |
| 5 | TABLE ACCESS BY INDEX ROWID| T4 | 10 | 10 | 10 |00:00:00.01 | 10 | 0 |
------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter("T3"."N"=1100)
4 - access("T3"."ID"="T4"."T3_ID")

在执行计划中可以看到在被驱动表上的连接列上加上索引后, buffer 从 121 下降到了 29

在驱动表的谓词条件列上 (T3 表的 n 列) 加上索引

SQL> create index t3_n on t3(n);

Index created.

SQL> select /*+ leading(t3) use_nl(t4) */ * from t3, t4 where t3.id = t4.t3_id and t3.n = 1100;

10 rows selected.

SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 89hnfwqakjghg, child number 0
-------------------------------------
select /*+ leading(t3) use_nl(t4) */ * from t3, t4 where t3.id = t4.t3_id and t3.n = 1100 Plan hash value: 2304842513 -------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
-------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.01 | 17 | 1 |
| 1 | NESTED LOOPS | | 1 | | 10 |00:00:00.01 | 17 | 1 |
| 2 | NESTED LOOPS | | 1 | 10 | 10 |00:00:00.01 | 7 | 1 |
| 3 | TABLE ACCESS BY INDEX ROWID| T3 | 1 | 1 | 1 |00:00:00.01 | 4 | 1 |
|* 4 | INDEX RANGE SCAN | T3_N | 1 | 1 | 1 |00:00:00.01 | 3 | 1 |
|* 5 | INDEX RANGE SCAN | T4_T3_ID | 1 | 10 | 10 |00:00:00.01 | 3 | 0 |
| 6 | TABLE ACCESS BY INDEX ROWID | T4 | 10 | 10 | 10 |00:00:00.01 | 10 | 0 |
------------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id):
--------------------------------------------------- 4 - access("T3"."N"=1100)
5 - access("T3"."ID"="T4"."T3_ID")

  在执行计划中可以看到在驱动表上的谓词条件列上加上索引后, buffer 从 29 继续下降到了 17

四. 小结

由此可见, 在 sql 调优时如果遇到表的连接方式是 nested loop:

首先,要确保结果集小的表为驱动表,结果集多的表为被驱动表。这不意味着记录多的表不能作为驱动表, 只要通过谓词条件过滤后得到的结果集比较小,也可以作为驱动表。

其次,在驱动表的谓词条件列以及被驱动表的连接列上加上索引,能够显著的提高执行性能。

最后,如果要查询的列都在索引中,避免回表查询列信息时,又将进一步提高执行性能。

https://blog.csdn.net/dataminer_2007/article/details/41826915

oracle 表连接 - nested loop 嵌套循环连接的更多相关文章

  1. 多表连接的三种方式详解 hash join、merge join、 nested loop

    在多表联合查询的时候,如果我们查看它的执行计划,就会发现里面有多表之间的连接方式.多表之间的连接有三种方式:Nested Loops,Hash Join 和 Sort Merge Join.具体适用哪 ...

  2. oracle 表连接 - hash join 哈希连接

    一. hash 连接(哈希连接)原理 指的是两个表连接时, 先利用两表中记录较少的表在内存中建立 hash 表, 然后扫描记录较多的表并探測 hash 表, 找出与 hash 表相匹配的行来得到结果集 ...

  3. Nested Loop,Sort Merge Join,Hash Join

    三种连接工作方式比较: Nested loops 工作方式是从一张表中读取数据,访问另一张表(通常是索引)来做匹配,nested loops适用的场合是当一个关联表比较小的时候,效率会更高. Merg ...

  4. 三大表连接方式详解之Nested loop join和 Sort merge join

    在早期版本,Oracle提供的是nested-loop join,两表连接就相当于二重循环,假定两表分别有m行和n行       如果内循环是全表扫描,时间复杂度就是O(m*n)       如果内循 ...

  5. Oracle表连接

    一个普通的语句select * from t1, t2 where t1.id = t2.id and t1.name = 'a'; 这个语句在什么情况下最高效? 表连接分类: 1. 嵌套循环连接(N ...

  6. Oracle 表连接方式分析 .

    一 引言 数据仓库技术是目前已知的比较成熟和被广泛采用的解决方案,用于整和电信运营企业内部所有分散的原始业务数据,并通过便捷有效的数据访问手段,可以支持企业内部不同部门,不同需求,不同层次的用户随时获 ...

  7. 浅谈SQL Server中的三种物理连接操作(HASH JOIN MERGE JOIN NESTED LOOP)

    简介 在SQL Server中,我们所常见的表与表之间的Inner Join,Outer Join都会被执行引擎根据所选的列,数据上是否有索引,所选数据的选择性转化为Loop Join,Merge J ...

  8. Merge join、Hash join、Nested loop join对比分析

    简介 我们所常见的表与表之间的Inner Join,Outer Join都会被执行引擎根据所选的列,数据上是否有索引,所选数据的选择性转化为Loop Join,Merge Join,Hash Join ...

  9. 浅谈SQL Server中的三种物理连接操作(Nested Loop Join、Merge Join、Hash Join)

    简介 在SQL Server中,我们所常见的表与表之间的Inner Join,Outer Join都会被执行引擎根据所选的列,数据上是否有索引,所选数据的选择性转化为Loop Join,Merge J ...

随机推荐

  1. P1969积木大赛

    这是2018与2013提高组的真题,可怕,,原题出了两年,是个纯模拟. 读完题后就想写一个朴素的模拟,先遍历层数,再把达到层数的宽度#存起来,再判断是否连续,如果不连续ans++,然后每一次循环都要初 ...

  2. 同一台电脑管理多个SSH KEY

    同一台电脑关于多个SSH KEY管理 笔者之前为电脑中的homestead虚拟机配置过id_rsa,但现在因为想在github上搭建基于hexo的博客,所以需要配置github的ssh key,因此产 ...

  3. Spark出现java.lang.stackoverflowerror的解决方法

    正在测试的程序需要多次迭代(400+次),每次迭代有复杂的运算 迭代到100多次的时候报java.lang.stackoverflowerror的错误 解决方法:先checkpoint()再count ...

  4. CyclicBarrier、CountDownLatch、Callable、FutureTask、thread.join() 、wait()、notify()、Condition

    CyclicBarrier使用: import java.util.Random; import java.util.concurrent.BrokenBarrierException; import ...

  5. TMS320F28335——SPI使用笔记

    一.SPI硬件接口 GPIO54    -------    SPISIMOA GPIO55    -------    SPISOMIA GPIO56    -------    SPCLK GPI ...

  6. 剑指offer-对称二叉树-树-python

    题目描述 请实现一个函数,用来判断一颗二叉树是不是对称的.注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的.   # -*- coding:utf-8 -*- # class TreeNo ...

  7. LNMP环境搭建哈哈

    经过一番折腾,终于将LNMP环境搭建完成了.本文介绍的LNMP环境是在windows的Oracle VM VirtualBox中的Centos虚拟机上搭建的,各个软件的版本为:Centos7 + Ng ...

  8. 基于maven的javaweb项目模块化开发

    转自:https://my.oschina.net/kingfire/blog/273381 基于maven的javaweb项目模块化开发 引言 考虑团队拥有多个类似项目的情况,比如一些功能差异不大的 ...

  9. MapReduce单机提交(待稿)

    MR 提交方式源码 提交方式: 1,开发-> jar -> 上传到集群中的某一个节点 -> hadoop jar ooxx.jar ooxx in out 2,嵌入[linux,wi ...

  10. 洛谷 P2704 [NOI2001]炮兵阵地 (状态压缩DP+优化)

    题目描述 司令部的将军们打算在NM的网格地图上部署他们的炮兵部队.一个NM的地图由N行M列组成,地图的每一格可能是山地(用"H" 表示),也可能是平原(用"P" ...