如果子查询(in/exists/not in/not exists)没能展开(unnest),在执行计划中就会产生 FILTER,FILTER 类似嵌套循环,FILTER 的算法与标量子查询一模一样。

    现有如下 SQL 以及其执行计划。

    1. select ename, deptno
    2. from emp
    3. where exists (select deptno
    4. from dept
    5. where emp.deptno = dept.deptno
    6. and dname = 'RESEARCH'
    7. and rownum = 1);
    1. Execution Plan
    2. ----------------------------------------------------------
    3. Plan hash value: 3414630506
    4. -------------------------------------------------------------------------------------
    5. | Id | Operation | Name | Rows | Bytes | Cost(%CPU)|Time |
    6. -------------------------------------------------------------------------------------
    7. | 0 | SELECT STATEMENT | | 5 | 45 | 6 (0)|00:00:01|
    8. |* 1 | FILTER | | | | | |
    9. | 2 | TABLE ACCESS FULL | EMP | 14 | 126 | 3 (0)|00:00:01|
    10. |* 3 | COUNT STOPKEY | | | | | |
    11. |* 4 | TABLE ACCESS BY INDEX ROWID| DEPT | 1 | 13 | 1 (0)|00:00:01|
    12. |* 5 | INDEX UNIQUE SCAN | PK_DEPT | 1 | | 0 (0)|00:00:01|
    13. -------------------------------------------------------------------------------------
    14. Predicate Information (identified by operation id):
    15. ---------------------------------------------------
    16. 1 - filter( EXISTS (SELECT 0 FROM "DEPT" "DEPT" WHERE ROWNUM=1 AND
    17. "DEPT"."DEPTNO"=:B1 AND "DNAME"='RESEARCH'))
    18. 3 - filter(ROWNUM=1)
    19. 4 - filter("DNAME"='RESEARCH')
    20. 5 - access("DEPT"."DEPTNO"=:B1)

    执行计划中,Id=1 就是 FILTER。注意观察 FILTER 所对应的谓词信息,FILTER 对应的谓词中包含有 EXISTS(子查询:B1)。运用光标移动大法我们可以知道 FILTER 下面有两个儿子(Id=2,Id=3)。

    现在我们来看一下上面 SQL 带有 A-Time 的执行计划。

    1. alter session set statistics_level=all;
    1. Session altered.
    1. select ename, deptno
    2. from emp
    3. where exists (select deptno
    4. from dept
    5. where emp.deptno = dept.deptno
    6. and dname = 'RESEARCH'
    7. and rownum = 1);
    1. ENAME DEPTNO
    2. ------------------------------ ----------
    3. SMITH 20
    4. JONES 20
    5. SCOTT 20
    6. ADAMS 20
    7. FORD 20
    1. select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));
    1. PLAN_TABLE_OUTPUT
    2. -------------------------------------------------------------------------------
    3. SQL_ID 6mq67by27udgm, child number 1
    4. -------------------------------------
    5. select ename, deptno from emp where exists (select deptno
    6. from dept where emp.deptno = dept.deptno and dname
    7. = 'RESEARCH' and rownum = 1)
    8. Plan hash value: 3414630506
    9. ----------------------------------------------------------------------------
    10. | Id | Operation | Name | Starts | E-Rows | A-Rows |
    11. ----------------------------------------------------------------------------
    12. | 0 | SELECT STATEMENT | | 1 | | 5 |
    13. |* 1 | FILTER | | 1 | | 5 |
    14. | 2 | TABLE ACCESS FULL | EMP | 1 | 14 | 14 |
    15. |* 3 | COUNT STOPKEY | | 3 | | 1 |
    16. |* 4 | TABLE ACCESS BY INDEX ROWID| DEPT | 3 | 1 | 1 |
    17. |* 5 | INDEX UNIQUE SCAN | PK_DEPT | 3 | 1 | 3 |
    18. ----------------------------------------------------------------------------
    19. Predicate Information (identified by operation id):
    20. ---------------------------------------------------
    21. 1 - filter( IS NOT NULL)
    22. 3 - filter(ROWNUM=1)
    23. 4 - filter("DNAME"='RESEARCH')
    24. 5 - access("DEPT"."DEPTNO"=:B1)

    为了方便排版,执行计划中省略了部分内容。Id=2 以及 Id=3 都是 FILTER 的儿子。Id=2 靠近 FILTER,我们可以把 Id=2 理解为 FILTER 的驱动表;Id=3 离 FILTER 比较远,可以把 Id=3 理解为 FILTER 的被驱动表。驱动表 EMP 只扫描了一次(Id=2,Starts=1),被驱动表被扫描了 3 次(Id=3,Starts=3)。

    FILTER 的算法与标量子查询一模一样,驱动表都是固定的(固定为主表),不可更改。

    从执行计划中我们可以看到,主表(EMP)通过连接列(DEPTNO)传值给子表(DEPT),:B1 就表示传值,主表(EMP)的连接列(DEPTNO)基数为 3,所以被驱动表(DEPT)被扫描了 3 次。FILTER 一般在整个 SQL 的快要执行完毕的时候执行(Filter 的 Id 一般小于等于 3)。

    请注意,执行计划中还有一种 FILTER,这类 FILTER 只起过滤作用,这类 FILTER 下面只有一个儿子,谓词中没有 exists,也没有绑定变量:B1,例子如下。

    1. PLAN_TABLE_OUTPUT
    2. -------------------------------------------------------------------------------------
    3. -------------------------------------------------------------------------------------
    4. | Id | Operation | Name | Rows | Bytes |Cost |
    5. -------------------------------------------------------------------------------------
    6. | 0 | SELECT STATEMENT | | 1 | 81 | 1618|
    7. | 1 | SORT AGGREGATE | | 1 | 81 | |
    8. |* 2 | FILTER | | | | |
    9. |* 3 | HASH JOIN OUTER | | | | |
    10. | 4 | NESTED LOOPS OUTER | | 642 | 38520 | 838|
    11. |* 5 | INDEX FAST FULL SCAN | PK_T_SEND_VEHICLE | 413 | 8260 | 12|
    12. | 6 | TABLE ACCESS BY INDEX ROWID| T_TASK_HEAD | 2 | 80 | 2|
    13. |* 7 | INDEX RANGE SCAN | IDX_TASK_VEHICLE_NO | 2 | | 1|
    14. | 8 | TABLE ACCESS FULL | T_TASK_DETAIL | 162K| 3337K| 777|
    15. -------------------------------------------------------------------------------------
    16. Predicate Information (identified by operation id):
    17. ---------------------------------------------------
    18. 2 - filter("TTASKDETAI2_"."IS_REAL"='N' OR "TTASKDETAI2_"."IS_REAL" IS NULL)
    19. 3 - access("TRANSTASKH0_"."TRANS_TASK_NO"="TTASKDETAI2_"."TRANS_TASK_NO"(+))
    20. 5 - filter(TRIM("SENDVEHICL1_"."SEND_VEHICLE_NO")='01037041212280054')
    21. 7 - access("TRANSTASKH0_"."SEND_VEHICLE_NO"(+)="SENDVEHICL1_"."SEND_VEHICLE_NO")

    我们在做 SQL 优化的时候,一般只需要关注 FILTER 下面有两个或者两个以上儿子这种 FILTER。关于如何避免执行计划中产生 FILTER 以及执行计划中产生了 FILTER 怎么优化,请参阅本书 7.1 节。