之前写过一篇文章,filter造成的性能问题,开头是这么写的:
filter这个词总让人很费解,它下一级可以挂 一个子节点,二个子节点,三个子节点...。
挂一个子节点意思过滤,如对全表进行扫描后,按照条件过滤,丢弃不满足条件的数据。
挂二个子节点类似是nest loop。
挂三个子节点类似1和2做nest loop,结果集再与3做nest loop(这个是我推测的)。
为了证明当时的观点,我们下面来做个试验:filter下挂一个节点:
挂一个节点时,下面的例子可以看到,对表扫描一次,就是对返回的结果集进行过滤,不过有趣的是当条件为假时,不会对表进行扫描。
SQL> select * from emp e where e.hiredate > sysdate - 10000 and e.hiredate < sysdate;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- -------------- ---------- ---------- ----------
7788 SCOTT ANALYST 7566 19-4月 -87 3000 20
7876 ADAMS CLERK 7788 23-5月 -87 1100 20
SQL> alter session set statistics_level=all;
会话已更改。
SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------
SQL_ID 982y5g2z9sn6f, child number 0
-------------------------------------
select * from emp e where e.hiredate > sysdate - 10000 and e.hiredate <
sysdate
Plan hash value: 3896240783
-------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------
|* 1 | FILTER | | 1 | | 2 |00:00:00.01 | 8 |
|* 2 | TABLE ACCESS FULL| EMP | 1 | 2 | 2 |00:00:00.01 | 8 |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(SYSDATE@!-10000<SYSDATE@!)
2 - filter(("E"."HIREDATE">SYSDATE@!-10000 AND "E"."HIREDATE"<SYSDATE@!))
SQL> select * from emp e where e.hiredate > sysdate - 10000 and e.hiredate < sysdate - 10000;
未选定行
SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
SQL_ID 0pywmdg63gf0m, child number 0
-------------------------------------
select * from emp e where e.hiredate > sysdate - 10000 and e.hiredate
< sysdate - 10000
Plan hash value: 3896240783
---------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time |
---------------------------------------------------------------------------
|* 1 | FILTER | | 1 | | 0 |00:00:00.01 |
|* 2 | TABLE ACCESS FULL| EMP | 0 | 1 | 0 |00:00:00.01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(SYSDATE@!-10000<SYSDATE@!-10000)
2 - filter(("E"."HIREDATE">SYSDATE@!-10000 AND
"E"."HIREDATE"<SYSDATE@!-10000))
filter下挂两个节点:
挂两个节点时,emp扫描了一次有14条记录,类似于nested loops, 而扫描如果是nested loop的话,则应该是14次,但是此处dept是3次。这是因为filter维护了一个hash table,如果有重复的记录可以减少扫描次数。此处由于emp中的deptno只有三个值,所以只扫描3次。
SQL> select /*+rule*/count(*) from empwhere not exists (select 1
from dept
where dept.dname = 'SALE'
and dept.deptno = emp.deptno);
COUNT(*)
----------
14
SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------------
SQL_ID 0wjfqsm43j4z5, child number 0
-------------------------------------
select /*+rule*/count(*) from emp where not exists (select 1 from dept
where dept.dname = 'SALE' and dept.deptno = emp.deptno)
Plan hash value: 3536434453
------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | A-Rows | A-Time | Buffers |
------------------------------------------------------------------------------------------
| 1 | SORT AGGREGATE | | 1 | 1 |00:00:00.01 | 13 |
|* 2 | FILTER | | 1 | 14 |00:00:00.01 | 13 |
| 3 | TABLE ACCESS FULL | EMP | 1| 14 |00:00:00.01 | 7 |
|* 4 | TABLE ACCESS BY INDEX ROWID| DEPT | 3 | 0 |00:00:00.01 | 6 |
|* 5 | INDEX UNIQUE SCAN | PK_DEPT | 3 | 3 |00:00:00.01 | 3 |
------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter( IS NULL)
4 - filter("DEPT"."DNAME"='SALE')
5 - access("DEPT"."DEPTNO"=:B1)
filter下挂三个节点:
挂三个节点时,每当emp返回一条数据,都会驱动dept和bouns分别执行一次(注意的是在执行连接时每次只能两个表进行连接),由于filter使用了缓存技术,所以看到的dept只是扫描了3次,bonus扫描了14次。
SQL> select /*+rule*/count(*) from empwhere not exists (select 1
from dept
where dept.dname = 'SALE'
and dept.deptno = emp.deptno)
and not exists (select 1 from bonus where bonus.ename = emp.ename);
COUNT(*)
----------
14
SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));
PLAN_TABLE_OUTPUT
-------------------------------------
SQL_ID 2wss1r100vqd9, child number 0
-------------------------------------
select /*+rule*/* from emp where not exists (select 1 from dept
where dept.dname = 'SALE' and dept.deptno = emp.deptno) and not
exists (select 1 from bonus where bonus.ename = emp.ename)
Plan hash value: 1445856646
-----------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------
|* 1 | FILTER | | 1 | 14 |00:00:00.01 | 56 |
| 2 | TABLE ACCESS FULL | EMP | 1 | 14 |00:00:00.01 | 8 |
|* 3 | TABLE ACCESS BY INDEX ROWID| DEPT | 3 | 0 |00:00:00.01 | 6 |
|* 4 | INDEX UNIQUE SCAN | PK_DEPT | 3 | 3 |00:00:00.01 | 3 |
|* 5 | TABLE ACCESS FULL | BONUS | 14 | 0 |00:00:00.01 | 42 |
-----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(( IS NULL AND IS NULL))
3 - filter("DEPT"."DNAME"='SALE')
4 - access("DEPT"."DEPTNO"=:B1)
5 - filter("BONUS"."ENAME"=:B1)
SQL> select count(*) from emp
where not exists (select 1
from dept
where dept.dname = 'SALE'
and dept.deptno = emp.deptno);
COUNT(*)
----------
14