前面章节中描述的规则适用于绝大部分的执行计划。虽然如此,还是有一些特殊情况。通常可以通过观察操作获知执行计划做了哪些事情,它们应用的谓词,它们是在哪些表上执行的以及它们的运行时行为(尤其是Starts和A-Rows列)。接下来的小节介绍了从众多可能的情况中挑选出来的三个例子。注意以下例子都是对special_cases.sql脚本生成输出的摘录。
1.select子句中的子查询
这个例子展示了在select子句中包含一个子查询的查询语句的执行计划是什么样子的。查询及其执行计划如下所示:
SELECT ename, (SELECT dname FROM dept WHERE
dept.deptno = emp.deptno)
FROM emp;
2 -
access("DEPT"."DEPTNO"=:B1)
奇怪的是,在这个执行计划中操作0有多个子操作。如果仔细观察Starts列,就会注意到尽管操作1和操作2被执行了三次,操作3仅被执行了一次。还要注意操作1和操作2,因为它们引用了dept表实现了子查询。这个不寻常的执行计划按以下步骤执行各个操作。
(1)操作3,也就是第一个被执行的操作,扫描emp表并将所有的数据返回给它的父操作(0)。
(2)对于操作3返回的每一行数据,子查询都应该被执行一次。然而,在本例中SQL引擎也缓存了结果,因此子查询只是为deptno列中的每个不重复值都执行了一次。
(3)为执行子查询,操作2通过应用"DEPT"."DEPTNO"=:B1访问谓词来扫描deptpk索引,提取rowid,并将它们返回给它的父操作(1)。绑定变量(B1)用来将需要检索的值传递给子查询。然后操作1使用这些rowid访问dept表,并将数据传递给它的父操作(0)。
(4)操作0将数据发送给调用者。
2.WHERE子句中的子查询#1
这个例子展示一个与在WHERE子句中包含着子查询的查询语句有关的特殊执行计划。该查询及其执行计划如下所示:
SELECT deptno
FROM dept
WHERE deptno NOT IN (SELECT deptno FROM
emp);
1 - filter(NOT EXISTS(select O FROM
"EMP" "EMP" WHERE LNNVL("DEPTNO":B1)))
2 -
filter(LNNVL("DEPTNO":B1))
警告 这个查询是v$sql_plan和v$sql_plan_statistics_all 视图给出错误信息的另一个案例。在本例中, EXPLAIN PLAN显示如上所示的正确谓词。错误显示的谓词是与操作1有关的那个: 1 -
filter(IS NULL)
乍一看,这个执行计划是由两个独立操作组成的。如果仔细观察Starts列,会注意到某些地方有点奇怪。事实上,尽管父操作(1)只被执行了一次,但子操作(2)却被执行了四次。实际上,该执行计划是按照以下步骤执行各个操作的。
(1)操作1,也就是第一个被执行的操作,扫描dept pk索引。对于deptno列中的每个值,都会执行操作2。就像过滤谓词显示的那样,操作2应用NOT EXISTS (select O FROM
"EMP" "EMP" WHERE LNNVL("DEPTNO":B1)) 子查询。注意,查询优化器将NOT IN转化为了NOT EXISTS。 绑定变量(B1)用来向子查询传递需要检索的值。
(2)操作2扫描emp表,应用LNNVL("DEPTNO":B1)过滤谓词,并将数据返回给它的父操作(1)。
(3)对于满足过滤谓词的每条数据,操作1都将其传递给它的父操作(0)。
(4)操作0将数据发送给调用者。
对于同一个查询语句,查询优化器还有可能生成下面的执行计划。但是,因为它使用了一个仅从 11.1版本开始可用的特性(NULL-aware
anti-join),不要指望在10.2版本中看见这种类型的执行计划。(依我看来,这个执行计划远比上一个更容易读取。)
3.WHERE子句中的子查询#2
这个例子是上一个的扩展。它也涉及在WHERE子句中的子查询。之所以展示它,是因为想提醒大家注意这样的事实:即使实现子查询的编码远比一个简单的查找复杂得多,查询优化器也能够生成类似前面小节中讨论的那种执行计划。该查询及其执行计划如下:
select * FROM t1
WHERE n1=8 AND n2 IN (select t2.n1 FROM
t2,t3 WHERE t2.id = t3.id AND t3.n1 =4);
警告 这个查询是v$sql_plan和v$sql_plan_statistics_all 视图给出错误信息的另一个案例。在本例中,EXPLAIN PLAN显示如上所示的正确谓词。错误显示的谓词是与操作2的过滤条件相关的那个:
2 - access("N1"=8)
filter( IS NOT NULL)
同样在本例中,如果仔细观察Starts列,会注意到某些地方有点奇怪。直到2操作为止都是执行了一次,而从3到5的操作却执行了13次。该执行计划按以下步骤执行各个操作。
(1)操作2,也就是第一个被执行的操作,通过扫描i1索引来应用"N1"=8访问谓词。它从索引中提取的,对于满足访问谓词的键,不仅是rowid而且还有n2列的值。对于n2列中的每个不重复值,子查询(操作3到5)都被执行一次。这是通过应用过滤谓词来完成的。注意查询优化器将IN转换成了EXISTS。子查询实施的联接是通过一个散列联接实现的,这是一个无关联组合操作。
(2)操作4,散列联接的第一个子操作,通过全表扫描读取t3表并将满足"T3"."N1"=4过滤谓词的数据返回给它的父操作(3)。
(3)操作5,散列联接的第二个子操作,通过全表扫描读取t2表并将满足"T2"."N1"=:B1过滤谓词的数据返回给它的父操作(3)。绑定变量(B1)用来向子查询传递需要检索的值。
(4)操作3联接由操作4和5传递过来的两组结果集。当至少有一行数据被找到时,它将数据返回给它的父操作(2)。
(5)对于每条满足由子查询实现的条件的数据,操作2都向其父操作(1)传递一个rowid。
(6)操作1使用从其子操作(2)接收的rowid来访问t1表并提取它的各个列。它将数据传递给它的父操作(0)。
(7)操作0将数据发送给调用者。