子查询非嵌套(Subquery Unnesting):当 where 子查询中有 in、not in、exists、not exists 等,CBO 会尝试将子查询展开(unnest),从而消除 FILTER,这个过程就叫作子查询非嵌套。子查询非嵌套的目的就是消除 FILTER。
现有如下 SQL 及其执行计划(Oracle11.2.0.1)。
select ename, deptno
from emp
where exists (select deptno
from dept
where dname = 'CHICAGO'
and emp.deptno = dept.deptno
union
select deptno
from dept
where loc = 'CHICAGO'
and dept.deptno = emp.deptno);
6 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 2705207488
-------------------------------------------------------------------------------------
| Id |Operation | Name | Rows | Bytes | Cost(%CPU)|Time |
-------------------------------------------------------------------------------------
| 0 |SELECT STATEMENT | | 5 | 45 | 15 (40)|00:00:01|
|* 1 | FILTER | | | | | |
| 2 | TABLE ACCESS FULL | EMP | 14 | 126 | 3 (0)|00:00:01|
| 3 | SORT UNIQUE | | 2 | 24 | 4 (75)|00:00:01|
| 4 | UNION-ALL | | | | | |
|* 5 | TABLE ACCESS BY INDEX ROWID| DEPT | 1 | 13 | 1 (0)|00:00:01|
|* 6 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)|00:00:01|
|* 7 | TABLE ACCESS BY INDEX ROWID| DEPT | 1 | 11 | 1 (0)|00:00:01|
|* 8 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)|00:00:01|
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter( EXISTS ( (SELECT "DEPTNO" FROM "DEPT" "DEPT" WHERE
"DEPT"."DEPTNO"=:B1 AND "DNAME"='CHICAGO')UNION (SELECT "DEPTNO" FROM "DEPT"
"DEPT" WHERE "DEPT"."DEPTNO"=:B2 AND "LOC"='CHICAGO')))
5 - filter("DNAME"='CHICAGO')
6 - access("DEPT"."DEPTNO"=:B1)
7 - filter("LOC"='CHICAGO')
8 - access("DEPT"."DEPTNO"=:B1)
执行计划中出现了 FILTER,驱动表因此被固定为 EMP。假设 EMP 有几百万甚至几千万行数据,那么该 SQL 效率就非常差。
现在将上述 SQL 改写如下。
select ename, deptno
from emp
where exists (select 1
from (select deptno
from dept
where dname = 'CHICAGO'
union
select deptno from dept where loc = 'CHICAGO') a
where a.deptno = emp.deptno);
6 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 4243948922
------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 110 | 12 (25)| 00:00:01 |
|* 1 | HASH JOIN SEMI | | 5 | 110 | 12 (25)| 00:00:01 |
| 2 | TABLE ACCESS FULL | EMP | 14 | 126 | 3 (0)| 00:00:01 |
| 3 | VIEW | | 2 | 26 | 8 (25)| 00:00:01 |
| 4 | SORT UNIQUE | | 1 | 24 | 8 (63)| 00:00:01 |
| 5 | UNION-ALL | | | | | |
|* 6 | TABLE ACCESS FULL| DEPT | 1 | 13 | 3 (0)| 00:00:01 |
|* 7 | TABLE ACCESS FULL| DEPT | 1 | 11 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("A"."DEPTNO"="EMP"."DEPTNO")
6 - filter("DNAME"='CHICAGO')
7 - filter("LOC"='CHICAGO')
对 SQL 进行等价改写之后,消除了 FILTER。为什么要消除 FILTER 呢?因为 FILTER 的驱动表是固定的,一旦驱动表被固定,那么执行计划也就被固定了。对于 DBA 来说这并不是好事,因为一旦固定的执行计划本身是错误的(低效的),就会引起性能问题,想要提升性能必须改写 SQL 语句,但是这时 SQL 已经上线,无法更改,所以,一定要消除 FILTER。
很多公司都有开发 DBA,开发 DBA 很大一部分的工作职责就是:必须保证 SQL 上线之后,每个 SQL 语句的执行计划都是可控的,这样才能尽可能避免系统中 SQL 越跑越慢。
下面我们继续对上述 SQL 进行等价改写。
select ename, deptno
from emp
where deptno in (select deptno
from dept
where dname = 'CHICAGO'
union
select deptno from dept where loc = 'CHICAGO');
6 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 2842951954
----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 9 | 198 | 12 (25)| 00:00:01 |
|* 1 | HASH JOIN | | 9 | 198 | 12 (25)| 00:00:01 |
| 2 | VIEW | VW_NSO_1 | 2 | 26 | 8 (25)| 00:00:01 |
| 3 | SORT UNIQUE | | 2 | 24 | 8 (63)| 00:00:01 |
| 4 | UNION-ALL | | | | | |
|* 5 | TABLE ACCESS FULL| DEPT | 1 | 13 | 3 (0)| 00:00:01 |
|* 6 | TABLE ACCESS FULL| DEPT | 1 | 11 | 3 (0)| 00:00:01 |
| 7 | TABLE ACCESS FULL | EMP | 14 | 126 | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("DEPTNO"="DEPTNO")
5 - filter("DNAME"='CHICAGO')
6 - filter("LOC"='CHICAGO')
将 SQL 改写为 in 之后,也消除了 FILTER。
如何才能产生 FILTER 呢?我们只需要在子查询中添加/*+ no_unnest */
。
select ename, deptno
from emp
where deptno in (select /*+ no_unnest */ deptno
from dept
where dname = 'CHICAGO'
union
select deptno from dept where loc = 'CHICAGO');
6 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 2705207488
-------------------------------------------------------------------------------------
| Id |Operation | Name | Rows | Bytes | Cost(%CPU)|Time |
-------------------------------------------------------------------------------------
| 0 |SELECT STATEMENT | | 5 | 45 | 15 (40)|00:00:01|
|* 1 | FILTER | | | | | |
| 2 | TABLE ACCESS FULL | EMP | 14 | 126 | 3 (0)|00:00:01|
| 3 | SORT UNIQUE | | 2 | 24 | 4 (75)|00:00:01|
| 4 | UNION-ALL | | | | | |
|* 5 | TABLE ACCESS BY INDEX ROWID| DEPT | 1 | 13 | 1 (0)|00:00:01|
|* 6 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)|00:00:01|
|* 7 | TABLE ACCESS BY INDEX ROWID| DEPT | 1 | 11 | 1 (0)|00:00:01|
|* 8 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)|00:00:01|
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter( EXISTS ( (SELECT /*+ NO_UNNEST */ "DEPTNO" FROM "DEPT" "DEPT"
WHERE "DEPTNO"=:B1 AND "DNAME"='CHICAGO')UNION (SELECT "DEPTNO" FROM "DEPT"
"DEPT" WHERE "DEPTNO"=:B2 AND "LOC"='CHICAGO')))
5 - filter("DNAME"='CHICAGO')
6 - access("DEPTNO"=:B1)
7 - filter("LOC"='CHICAGO')
8 - access("DEPTNO"=:B1)
大家可能会问,既然能通过 HINT(NO_UNNEST)让执行计划产生 FILTER,那么执行计划中如果产生了 FILTER,能否通过 HINT(UNNEST)消除 FILTER 呢?执行计划中的 FILTER 很少能够通过 HINT 消除,一般需要通过 SQL 等价改写来消除。
现在我们对产生 FILTER 的 SQL 添加 HINT(UNNEST)来尝试消除 FILTER。
select ename, deptno
from emp
where exists (select /*+ unnest */ deptno
from dept
where dname = 'CHICAGO'
and emp.deptno = dept.deptno
union
select deptno
from dept
where loc = 'CHICAGO'
and dept.deptno = emp.deptno);
6 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 2705207488
-------------------------------------------------------------------------------------
| Id |Operation | Name | Rows | Bytes | Cost(%CPU)|Time |
-------------------------------------------------------------------------------------
| 0 |SELECT STATEMENT | | 5 | 45 | 15 (40)|00:00:01|
|* 1 | FILTER | | | | | |
| 2 | TABLE ACCESS FULL | EMP | 14 | 126 | 3 (0)|00:00:01|
| 3 | SORT UNIQUE | | 2 | 24 | 4 (75)|00:00:01|
| 4 | UNION-ALL | | | | | |
|* 5 | TABLE ACCESS BY INDEX ROWID| DEPT | 1 | 13 | 1 (0)|00:00:01|
|* 6 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)|00:00:01|
|* 7 | TABLE ACCESS BY INDEX ROWID| DEPT | 1 | 11 | 1 (0)|00:00:01|
|* 8 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)|00:00:01|
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter( EXISTS ( (SELECT /*+ UNNEST */ "DEPTNO" FROM "DEPT" "DEPT" WHERE
"DEPT"."DEPTNO"=:B1 AND "DNAME"='CHICAGO')UNION (SELECT "DEPTNO" FROM "DEPT"
"DEPT" WHERE "DEPT"."DEPTNO"=:B2 AND "LOC"='CHICAGO')))
5 - filter("DNAME"='CHICAGO')
6 - access("DEPT"."DEPTNO"=:B1)
7 - filter("LOC"='CHICAGO')
8 - access("DEPT"."DEPTNO"=:B1)
执行计划中还是有 FILTER。再次强调:执行计划中如果产生了 FILTER,一般是无法通过 HINT 消除的,一定要注意执行计划中的 FILTER。
请注意,虽然我们一直强调要消除执行计划中的 FILTER,本意是要保证执行计划是可控的,并不意味着执行计划产生了 FILTER 就一定性能差,相反有时候我们还可以用 FILTER 来优化 SQL。
哪些 SQL 写法容易产生 FILTER 呢?当子查询语句含有 exists 或者 not exists 时,子查询中有固化子查询关键词(union/union all/start with connect by/rownum/cube/rollup),那么执行计划中就容易产生 FILTER,例如,exists 中有 rownum 产生 FILTER。
select ename, deptno
from emp
where exists (select deptno
from dept
where loc = 'CHICAGO'
and dept.deptno = emp.deptno
and rownum <= 1);
6 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 3414630506
-------------------------------------------------------------------------------------
| Id |Operation | Name | Rows | Bytes | Cost (%CPU)|Time |
-------------------------------------------------------------------------------------
| 0 |SELECT STATEMENT | | 5 | 45 | 6 (0)|00:00:01|
|* 1 | FILTER | | | | | |
| 2 | TABLE ACCESS FULL | EMP | 14 | 126 | 3 (0)|00:00:01|
|* 3 | COUNT STOPKEY | | | | | |
|* 4 | TABLE ACCESS BY INDEX ROWID| DEPT | 1 | 11 | 1 (0)|00:00:01|
|* 5 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)|00:00:01|
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter( EXISTS (SELECT 0 FROM "DEPT" "DEPT" WHERE ROWNUM<=1 AND
"DEPT"."DEPTNO"=:B1 AND "LOC"='CHICAGO'))
3 - filter(ROWNUM<=1)
4 - filter("LOC"='CHICAGO')
5 - access("DEPT"."DEPTNO"=:B1)
exists 中有树形查询产生 FILTER。
select *
from dept
where exists (select null
from emp
where dept.deptno = emp.deptno
start with empno = 7698
connect by prior empno = mgr);
Execution Plan
----------------------------------------------------------
Plan hash value: 4210865686
-------------------------------------------------------------------------------------
| Id |Operation | Name | Rows | Bytes |Cost (%CPU)|
-------------------------------------------------------------------------------------
| 0 |SELECT STATEMENT | | 1 | 20 | 9 (0)|
|* 1 | FILTER | | | | |
| 2 | TABLE ACCESS FULL | DEPT | 4 | 80 | 3 (0)|
|* 3 | FILTER | | | | |
|* 4 | CONNECT BY NO FILTERING WITH SW (UNIQUE)| | | | |
| 5 | TABLE ACCESS FULL | EMP | 14 | 154 | 3 (0)|
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter( EXISTS (SELECT 0 FROM "EMP" "EMP" WHERE "EMP"."DEPTNO"=:B1 START WITH
"EMPNO"=7698 CONNECT BY "MGR"=PRIOR "EMPNO"))
3 - filter("EMP"."DEPTNO"=:B1)
4 - access("MGR"=PRIOR "EMPNO")
filter("EMPNO"=7698)
为什么 exists/not exists 容易产生 FILTER,而 in 很少会产生 FILTER 呢?当子查询中有固化关键字(union/union all/start with connect by/rownum/cube/rollup),子查询会被固化为一个整体,采用 exists/not exists 这种写法,这时子查询中有主表连接列,只能是主表通过连接列传值给子表,所以 CBO 只能选择 FILTER。而我们如果将 SQL 改写为 in/not in 这种写法,子查询虽然被固化为整体,但是子查询中没有主表连接列字段,这个时候 CBO 就不会选择 FILTER。
START WITH CONNECT BY
SELECT ... FROM +表名 WHERE + 条件3 START WITH + 条件1 CONNECT BY PRIOR + 条件2
条件1表示我数据的切入点,也就是我第一条数据从哪里开始.
条件2是连接条件,其中用PRIOR表示上一条记录,例如CONNECT BY PRIOR ID = PID,意思就是上一条记录的ID是本条记录的PID
条件3表示条件12执行遍历结果之后再进行条件约束.
首先 我们查询所有表示这样的,
SELECT * FROM start_demo start with id = '00001' Connect By Prior id = pid
start with id = ‘00001’ 表示切入点,也就是我的第一条数据
Connect By Prior id = pid 表示我的上一条数据的id是我当前数据的pid(如果不是就表明不是当前节点)
反之
SELECT * FROM start_demo start with id = '00113' Connect By Prior PID = id ---上一条记录的PID是本条记录的ID