在实际应用中,这个规则不是完全正确的。ORACLE的SQL内部步骤的执行顺序与其计划中的展现,会有一定的差别,如果不仔细分析,而且一味相信文档,那么可能会感觉很迷惑。比如在标量子查询中(scalary subquery),执行计划的显示会非常让人困惑,如:
|
SQL> select * from a; ID NAME ---------- ---------------------------------- 1 a 2 b 3 c 3 rows selected. SQL> select * from b; ID NAME ---------- ---------------------------------- 1 x1 2 x2 2 rows selected. SQL> SELECT a.ID,a.NAME,(SELECT b.ID FROM b WHERE a.ID=b.ID) bid FROM a; 执行计划 ---------------------------------------------------------- Plan hash value: 2657529235 -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 3 | 60 | 3 (0)| 00:00:01 | |* 1 | TABLE ACCESS FULL| B | 1 | 13 | 3 (0)| 00:00:01 | | 2 | TABLE ACCESS FULL| A | 3 | 60 | 3 (0)| 00:00:01 | -------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("B"."ID"=:B1) |
如果按照文档的的分析,显然ID=2的与ID=1的是同等级的,ID=1的在ID=2的上面,那么最后执行计划的顺序应该是1---->2----->0,但是分析下,显然不是这样的顺序,肯定是必须获得a.id之后,才能用a.id去查找B。通过谓词中的"B"."ID"=:B1可以看出来,:B1,类似于绑定变量,这里就2张表,而且根据SQL查询,肯定来源于A.ID。所以对于标量子查询的计划,应该是2---->1----->0,而且2与1的操作是类似于NESTED LOOPS(与其不同的是,标量子查询的驱动表是inner table)的操作,每1个A的行,都会执行一次B,当然,ORACLE内部肯定是有优化的,这种优化就是会缓存已经匹配的A.ID值,遇到相同的,不会重复扫描B。可以通过DBMS_XPLAN.DISPLAY_CURSOR详细看看如何执行的:
|
SQL> @display_cursor SQL_ID caq6tcx266xnq, child number 1 ------------------------------------- SELECT a.ID,a.NAME,(SELECT b.ID FROM b WHERE a.ID=b.ID) bid FROM a Plan hash value: 2657529235 ------------------------------------------------------------------------------------ | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | ------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1 | | 3 |00:00:00.01 | 8 | |* 1 | TABLE ACCESS FULL| B | 3 | 1 | 2 |00:00:00.01 | 21 | | 2 | TABLE ACCESS FULL| A | 1 | 3 | 3 |00:00:00.01 | 8 | ------------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("B"."ID"=:B1) |
其中A共3行,访问B 3次,返回B 2行,因为有一行不匹配,由A的行驱动访问B。因为这里A.ID无重复值,下面插入一行id=1的,因为id=1已经在A表中存在,因此,标量子查询有缓存,所以对B的扫描还是3次,而不是4次,如下:
|
SQL> INSERT INTO a VALUES(1,'d'); 1 row created. SQL> COMMIT; Commit complete. SQL> @display_cursor SQL_ID caq6tcx266xnq, child number 0 ------------------------------------- SELECT a.ID,a.NAME,(SELECT b.ID FROM b WHERE a.ID=b.ID) bid FROM a Plan hash value: 2657529235 ------------------------------------------------------------------------------------ | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | ------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1 | | 4 |00:00:00.01 | 8 | |* 1 | TABLE ACCESS FULL| B | 3 | 1 | 2 |00:00:00.01 | 21 | | 2 | TABLE ACCESS FULL| A | 1 | 4 | 4 |00:00:00.01 | 8 | ------------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("B"."ID"=:B1) |
从计划中可以看到,虽然A是4行,但是因为DISTINCT A.ID是3行,所以还是扫描B 3次,其中ID=1的访问一次即缓存结果,通过A-ROWS可以看到B还是返回2行,而不是3行。所以不要看到标量子查询就认为效率不行,标量子查询和FILTER类似,如果能够对标量子查询走索引扫描,甚至UNIQUE INDEX SCAN,如果主表查询的行重复值特别多,效率还是很高的,标量子查询在一定程度上,消除了JOIN,经常在查询这种对应某表的行,需要匹配另一表的某个列值,比JOIN效率高(当然,既然类似于NESTED LOOPS了,结果集肯定不会很大,不然效率会差,这个tom的高效设计上有详细的讲解)。
本文主要就是讲解下,执行计划反应了SQL的执行顺序,但是如果通过执行计划准确知道SQL中的执行顺序,并不是只要了解文档中说的规则就可以了,在实际应用中,可能会碰到这样那样的问题,文档当然很少有错误,但是文档大多说的都是普遍的规则,SO,在学习过程中,对不理解的问题,要随时质疑,并论证之。