CONNECT BY WITH FILTERING操作
这个操作是用来处理层次查询的。它的特征是有两个子操作。第一个用来获取层次的顶级,第二个为层次中的每一个级别都执行一次。
下面是一个样例查询及其计划(图10-8展示了它的父-子关系的图形表示)。注意,该执行计划是在11.2版本下生成的(原因在之前解释过了):
select level, rpad('-',level-1,'-')||ename
AS ename,prior ename AS manager
FROM emp
START WITH mgr IS NULL
CONNECT BY PRIOR empno = mgr;
警告 上面的查询代表了v$sql_plan和v$sql_plan_statistics_all视图给出错误信息的另一种情况。在本例中,EXPLAIN PLAN显示了上面显示的正确谓词,错误显示的谓词是与操作1关联的那个:
1 - access("MGR"=PRIOR NULL)
此外,与操作6关联的访问谓词在11.1及之前的版本中都是错误的。
在这个执行计划中,
CONNECT BY WITH FILTERING操作的第一个子操作是独立操作。不同的是,第二个子操作本身是一个关联组合操作。在这种情况下读取一个执行计划,你只需要简单地沿着关系树下行递归应用规则。
为了帮助你更容易地理解层次查询的执行计划,可以查看一下查询返回的数据:
P303
应用早前描述的规则,你会发现执行计划按以下方式执行各个操作。
(1)操作0有一个子操作(1)。它不可能是第一个被执行的操作。
(2)操作1有两个子操作(2和3),而且按升序排列时操作2排在第一。因此,执行从操作2开始。
(3)操作2扫描emp表,应用"MGR" IS NULL过滤谓词,然后将层次的根(KING)返回给它的父操作(1)。
(4)操作3是操作1的第二个子操作。因此它会为层次中的每一个级别都执行,在本例中执行四次。当然,之前讨论的关于NESTEDLOOPS操作的规则适用于操作3。第一个子操作(4)被执行了,对于它返回的每一行,会将内循环(由操作5和6操作组成)都执行一次。注意,正如预期的,操作4的A-Rows列与操作5和6的Starts列之间是匹配的。
(5)对于第一次执行,操作4通过CONNECT BY
PUMP操作获取层级的根。在本例中,级别1只有一条数据(KING)。 通过这个值,操作6通过应用"MGR"=PRIOR
"EMPNO"访问谓词(显示为"connect$_by$_pump$_002"."PRIOR
empno"="MGR")对emp_mgr_i索引做扫描,应用过滤谓词"MGR" IS NOT NULL,提取
出rowid,然后将它们返回给它的父操作(5)。操作5通过这些rowid访问emp表,并将数据返回给它的父操作(3)。
(6)对于操作4的第二次执行,做的每件事都与第一次执行的一样。唯一的不同是来自级别2的数据(JONES、BLAKE以及CLARK)被传递给操作4用于处理(一个接一个,每一行都会引发操作4的启动)。
(7)对于操作4的第三次执行,做的每件事都与第一次执行的一样。唯一的不同是来自级别3的数据(SCOTT、FORD、ALLEN、WARD、MARTIN、TURNER、JAMES以及MILLER)被传递给操作4用于处理。
(8)对于操作4的第四次和最后一次执行,做的每件事都与第一次执行的一样。唯一的不同是来自级别4的数据(ADAMS和SMITH)被传递给操作4用于处理。
(9)操作3获取从它的子操作传递过来的数据,然后将它们返回给它的父操作(1)。
(10)操作1应用"MGR"
IS NOT NULL过滤谓词。
(11)操作0将数据发送给调用者。
在10.2.0.3及之前的版本中,生成的执行计划有着细微的不同。如下例所示,CONNECT
BY WITH FILTERING操作有第三个子操作(操作8)。然而,在本例中,并没有执行它。操作8的Starts列中的值证实了这一点。实际上,仅当CONNECT BY WITH
FILTERING操作使用临时表空间时,才会执行第三个子操作。到那时候性能恐怕会严重下降。在10.2.0.4及之后的版本中已修复这个问题,这个问题被称为bug 5065418: