本帖最后由 dingjun123 于 2012-6-18 23:27 编辑
好久没有写东西了,长期忙于菠菜啊,想想,还是抽空写点东西吧,但是不知道写啥,后来一想,还是站在巨人的肩膀上吧,Jonathan lewis,大家都知道,从1988年就开始搞ORACLE,国际著名数据库大师,对ORACLE各种机制非常了解,他一直坚持写文章,每篇文章都很深入,他的文章尤其以优化居多,研究ORACLE各种内部机制。就从他的文章开始吧(注意:并不是原文翻译,会加入自己写的东西,核心内容应该差不多)。
lewis经典ORACLE著作:Practical Oracle 8i
Cost-Based Oracle Fundamentals
Oracle Core: Essential Internals for Troubleshooting
准备按这种结构来:
1)原文内容
2)文章回复与解答
3)点评
1.1 ANSI OUTER发表时间:2011-1-31
这个例子是ORACLE ANSI JOIN,ANSIJOIN是ORACLE 9i新特性,ANSI JOIN与ORACLE原来的JOIN语法不同(外连接用+,全完连接用UNION ALL实现),本例通过对比ANSI JOIN与ORACLE原始JOIN语法的区别。
建立的两个表在LMT管理的表空间下、8K block、1M uniform extent和freelist管理。禁用system statistics(CPU COSTING)。
DROP TABLE t1; create table t1 as select rownum -1 id, mod(rownum - 1,20) n1, lpad(rownum - 1,10,'0') v1, rpad('x',100) padding from all_objects where rownum <=4000 ;
DROP TABLE t2; create table t2 as select rownum -1 id, mod(rownum - 1,20) n1, lpad(rownum - 1,10,'0') v1, rpad('x',100) padding from all_objects where rownum <=4000 ;
create index t1_i1 on t1(id); create index t2_i1 on t2(id);
begin dbms_stats.gather_table_stats( ownname => user, tabname =>'T1', estimate_percent => 100, method_opt => 'for allcolumns size 1' );
dbms_stats.gather_table_stats( ownname => user, tabname =>'T2', estimate_percent => 100, method_opt => 'for allcolumns size 1' );
end; /
使用ANSI JOIN外连接,语句如下:
select /*+gather_plan_statistics */ t1.id, t1.n1, t1.v1, t2.n1 from t1 left join t2 on t2.id =t1.n1 and t1.n1 in (7, 11, 13) where t1.id = 15 ; ID N1 V1 N1 ---------- ---------- -------------------- ---------- 15 15 0000000015 dingjun123@ORADB>@displan PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------------------------- SQL_ID 9xccg0mu53241, child number 0 ------------------------------------- select /*+ gather_plan_statistics */ t1.id, t1.n1, t1.v1, t2.n1 from t1 left join t2 on t2.id = t1.n1 and t1.n1 in (7, 11,13) where t1.id = 15 Plan hash value:2591160116 -------------------------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | -------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 4 | | 1 | NESTED LOOPS OUTER | | 1 | 1 | 1 |00:00:00.01 | 4 | | 2 | TABLE ACCESS BY INDEX ROWID |T1 | 1 | 1 | 1 |00:00:00.01 | 4 | |* 3 | INDEX RANGE SCAN |T1_I1 | 1 | 1 | 1 |00:00:00.01 | 3 | | 4 | VIEW | | 1 | 1 | 0 |00:00:00.01 | 0 | |* 5 | FILTER | | 1 | | 0 |00:00:00.01 | 0 | | 6 | TABLE ACCESS BY INDEX ROWID| T2 | 0 | 1 | 0 |00:00:00.01 | 0 | |* 7 | INDEX RANGE SCAN | T2_I1| 0 | 1 | 0 |00:00:00.01 | 0 | -------------------------------------------------------------------------------------------------- PredicateInformation (identified by operation id): --------------------------------------------------- 3 - access("T1"."ID"=15) 5 -filter(("T1"."N1"=7 OR "T1"."N1"=11 OR"T1"."N1"=13)) 7 -access("T2"."ID"="T1"."N1") filter(("T2"."ID"=7 OR"T2"."ID"=11 OR "T2"."ID"=13)) 从执行计划上分析,ANSI JOIN的on条件,在计划里显示为“谓词传递”,将t1.n1的条件传递到了t2.id上。语句的含义是ORACLE会找满足t1.id=15的t1的所有行,并且t1.n1 in (7,11,13)的行的时候t2.id和t1.n1相等,其他均为外连接(t2端补null)。
注意FILTER,FILTER 类似于NESTED LOOPS( “conditional” filter), 这里的执行计划并不是6,7 先执行,而是会先判断是否满足FILTER 条件,如果满足执行6,7 ,否则不执行。因为t1.id=15 返回的t1.n1=15, 只返回一行,所以不满足谓词 5 - filter(("T1"."N1"=7OR "T1"."N1"=11 OR "T1"."N1"=13))条件,因此6,7 没有执行(通过STARTS 执行次数可以看出),实际未访问t2 索引。
那么用ORACLE原始语法改写,得到的计划是否等效呢?
select /*+gather_plan_statistics */ t1.id, t1.n1, t1.v1, t2.n1 from t1, t2 WHERE t1.id =15 ANDt2.ID(+)=CASE WHEN t1.n1 IN (7,11,13) THEN t1.n1 END;
PLAN_TABLE_OUTPUT ------------------------------------------------------------------------------------------------ SQL_ID 0xphawu4apgkx, child number 0 ------------------------------------- select /*+ gather_plan_statistics */ t1.id, t1.n1, t1.v1, t2.n1 from t1, t2 WHERE t1.id = 15 AND t2.ID(+)=CASE WHENt1.n1 IN (7,11,13) THEN t1.n1 END Plan hash value:3024481811 ------------------------------------------------------------------------------------------------ | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | ------------------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 4 | | 1 | NESTED LOOPS OUTER | | 1 | 1 | 1 |00:00:00.01 | 4 | | 2 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 1 | 1 |00:00:00.01 | 4 | |* 3 | INDEX RANGE SCAN | T1_I1| 1 | 1 | 1 |00:00:00.01 | 3 | | 4 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 1 | 0 |00:00:00.01 | 0 | |* 5 | INDEX RANGE SCAN | T2_I1| 1 | 1 | 0 |00:00:00.01 | 0 | ------------------------------------------------------------------------------------------------ PredicateInformation (identified by operation id): --------------------------------------------------- 3 - access("T1"."ID"=15) 5 -access("T2"."ID"=CASE "T1"."N1" WHEN 7THEN "T1"."N1" WHEN 11 THEN "T1"."N1" WHEN 13 THEN"T1"."N1" END ) 上面没有view,虽然第5步访问索引,但是因为access条件CASE语句返回NULL,所以实际虽然访问了一次索引(STARTS=1),但是未访问任何数据块(BUFFERS=0)。实际效率和ANSI JOIN的一样。
鉴于我先前的经验,ORACLE自己的老语法更易于理解,但是客户可能认为ANSI JOIN好,他们写惯了ANSI JOIN。
通过跟踪10053 事件,发现上面语句是用LATERAL 子查询改写的。 select /*+gather_plan_statistics */ t1.id, t1.n1, t1.v1, t2.n1 from t1, lateral ( ( select t2.n1 from t2 where t1.n1 in (7, 11, 13) and t2.id = t1.n1 ) )(+) t2 where t1.id = 15 ;
lateral ( * ERROR at line 9: ORA-00933: SQL command not properly ended
lateral类似于管道函数table,lateral创建一inline view, 允许内部包含子查询,此子查询还可以引用其他对象,外面可以查对应的列。 上面报错,对于lateral view可以使用22829 event来执行: dingjun123@ORADB> alter sessionset events '22829 trace name context forever'; 执行计划和ANSI JOIN一样,省略。
|