在多表关联的时候,一般情况下只能是两个表先关联,两表关联之后的结果再和其他表、结果集关联。
嵌套循环(NESTED LOOPS)
嵌套循环的算法:驱动表返回一行数据,通过连接列传值给被驱动表,驱动表返回多少行,被驱动表就要被扫描多少次。
如果 SQL中添加了HINT:FIRST_ROWS,在两表关联的时候,优化器更倾向于嵌套循环。
scott@orclpdb1:orclcdb> set autot trace
scott@orclpdb1:orclcdb>
scott@orclpdb1:orclcdb> select /*+ gather_plan_statistics use_nl(e,d) leading(e)*/
2 e.ename, e.job, d.dname
from emp e, dept d
4 where e.deptno = d.deptno;
14 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 3625962092
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 14 | 420 | 17 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 14 | 420 | 17 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 14 | 420 | 17 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL | EMP | 14 | 238 | 3 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| DEPT | 1 | 13 | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("E"."DEPTNO"="D"."DEPTNO")
Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1 (U - Unused (1))
---------------------------------------------------------------------------
3 - SEL$1 / E@SEL$1
U - use_nl(e,d)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
25 consistent gets
0 physical reads
0 redo size
1076 bytes sent via SQL*Net to client
493 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
14 rows processed
scott@orclpdb1:orclcdb>
declare
cursor cur_emp is
select ename, job, deptno from emp;
v_dname dept.dname%type;
begin
for x in cur_emp loop
select dname into v_dname from dept where deptno = x.deptno;
dbms_output.put_line(x.ename || ' ' || x.job || ' ' || v_dname);
end loop;
end;
尽量避免游标循环里面套用SQL(原因是纯天然的嵌套循环)
尽量避免游标里面套游标。
当两表使用外部进行
[oracle@MaxwellDBA ~]$ sqlplus sys/sys as sysdba
SQL*Plus: Release 19.0.0.0.0 - Production on Fri Oct 21 11:01:24 2022
Version 19.3.0.0.0
Copyright (c) 1982, 2019, Oracle. All rights reserved.
Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0
sys@cdb$root:orclcdb> ALTER SESSION SET CONTAINER=ORCLPDB1;
Session altered.
sys@cdb$root:orclcdb> conn SCOTT/TIGER@ORCLPDB1;
Connected.
scott@orclpdb1:orclcdb>
explain plan for select /*+ use_nl(d,e) leading(e) */
2 *
from dept d
4 left join emp e on d.deptno = e.deptno;
Explained.
scott@orclpdb1:orclcdb>
scott@orclpdb1:orclcdb> set linesize 200 pagesize 200
scott@orclpdb1:orclcdb>
scott@orclpdb1:orclcdb> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 2022884187
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 15 | 870 | 10 (0)| 00:00:01 |
| 1 | NESTED LOOPS OUTER| | 15 | 870 | 10 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL| DEPT | 4 | 80 | 3 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| EMP | 4 | 152 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter("D"."DEPTNO"="E"."DEPTNO"(+))
Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 2 (U - Unused (2))
---------------------------------------------------------------------------
1 - SEL$2BFA4EE4
U - leading(e)
2 - SEL$2BFA4EE4 / D@SEL$1
U - use_nl(d,e)
25 rows selected.
scott@orclpdb1:orclcdb>
[oracle@MaxwellDBA ~]$ sqlplus sys/sys as sysdba
SQL*Plus: Release 19.0.0.0.0 - Production on Fri Oct 21 11:01:24 2022
Version 19.3.0.0.0Copyright (c) 1982, 2019, Oracle. All rights reserved.
Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0sys@cdb$root:orclcdb> ALTER SESSION SET CONTAINER=ORCLPDB1;
Session altered.
sys@cdb$root:orclcdb> conn SCOTT/TIGER@ORCLPDB1;
Connected.
scott@orclpdb1:orclcdb>
explain plan for select /*+ use_nl(d,e) leading(e) */
2 *
from dept d
4 left join emp e on d.deptno = e.deptno;Explained.
scott@orclpdb1:orclcdb>
scott@orclpdb1:orclcdb> set linesize 200 pagesize 200
scott@orclpdb1:orclcdb>
scott@orclpdb1:orclcdb> select * from table(dbms_xplan.display);PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 2022884187---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 15 | 870 | 10 (0)| 00:00:01 |
| 1 | NESTED LOOPS OUTER| | 15 | 870 | 10 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL| DEPT | 4 | 80 | 3 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| EMP | 4 | 152 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------3 - filter("D"."DEPTNO"="E"."DEPTNO"(+))
Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 2 (U - Unused (2))
---------------------------------------------------------------------------1 - SEL$2BFA4EE4
U - leading(e)2 - SEL$2BFA4EE4 / D@SEL$1
U - use_nl(d,e)25 rows selected.
scott@orclpdb1:orclcdb>
use_nl(d,e) 表示让两表走嵌套循环,HINT时,SQL语句中的表有别名的时候用别名,否则HINT不生效。
leading(e) 表示让emp表作为驱动表
为什么两表关联外连接时,走嵌套却无法更改驱动表呢?
因为嵌套循环需要传值,主表传值给从表之后,如果发现从表没有关联上。显示为NULL即可。
因此两表关联是外连接的时候,走嵌套循环驱动表只能固定为主表。
如果外连接中从表有过滤条件,那么此时外连接会变为内连接。
scott@orclpdb1:orclcdb> explain plan for select /*+ use_nl(d,e) leading(e) */
2 *
from dept d
4 left join emp e on d.deptno = e.deptno
5 where e.sal < 3000;
Explained.
scott@orclpdb1:orclcdb> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 3625962092
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 12 | 696 | 15 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 12 | 696 | 15 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 12 | 696 | 15 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL | EMP | 12 | 456 | 3 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| DEPT | 1 | 20 | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter("E"."SAL"<3000)
4 - access("D"."DEPTNO"="E"."DEPTNO")
Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1 (U - Unused (1))
---------------------------------------------------------------------------
3 - SEL$AA565B58 / E@SEL$1
U - use_nl(d,e)
25 rows selected.
scott@orclpdb1:orclcdb>
为什么外连接的从表有过滤条件会变成内连接?
因为外连接的从表有过滤条件已经排除了从表与主表没有关联上显示为NULL的情况。
scott@orclpdb1:orclcdb> explain plan for select /*+ use_nl(d,e) leading(e) */
2 *
from dept d
4 left join emp e on d.deptno = e.deptno
5 where e.sal < 3000;Explained.
scott@orclpdb1:orclcdb> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 3625962092----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 12 | 696 | 15 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 12 | 696 | 15 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 12 | 696 | 15 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL | EMP | 12 | 456 | 3 (0)| 00:00:01 |
|* 4 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| DEPT | 1 | 20 | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------3 - filter("E"."SAL"<3000)
4 - access("D"."DEPTNO"="E"."DEPTNO")Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1 (U - Unused (1))
---------------------------------------------------------------------------3 - SEL$AA565B58 / E@SEL$1
U - use_nl(d,e)25 rows selected.
scott@orclpdb1:orclcdb>
Q1: 两表关联是否走NL是看两表关联之后返回的数据量多少?还是看驱动表返回的数据量多少?
若1(驱动表):N(N很大,被驱动表)关系,此刻即便驱动表返回数据量很少,也不能走NL,因为两表关联后返回的数据量会很大。
判断两表关联是否走NL 应该查看两表关联之后返回的数据量,数据量少,走NL, 数据量大走HASH JOIN
Q2: 大表是否可以当NL驱动表?
若大表过滤后返回的数据量很少就可以当NL驱动表。
小表与大表关联,可以考虑让小表驱动大表,大表走联接列索引(如果大表有过滤条件与连接列组合起来创建组合索引),从而避免大表被全表扫描。