ANSI Hint
原文作者:Jonathan lewis
原文地址:https://jonathanlewis.wordpress.com/2020/03/22/ansi-hinting/
译文如下:
在过去,我曾对“ANSI”样式的SQL在标记或标识查询块时引入了额外的复杂性,这意味着很难正确地提示。这是一篇笔记,说明优化器如何首先将“ANSI”SQL转换为“Oracle”语法。我将用经典的Oracle格式编写一个简单的4表联接,并用它的查询块名和完全限定的表别名来分析执行计划;然后我将转换成等效的ANSI 样式,并重复分析查询块名和别名,最后,我将用经典的Oracle语法重写查询,该语法重新生成从ANSI表单获得的查询块名和完全限定的表别名。
我们首先创建4个表及索引(使用一个脚本,我已经在各种测试中使用了几年,但是我将要展示的结果来自19c):
rem rem Script: ansi_hint_3.sql rem Author: Jonathan Lewis rem Dated: June 2014 rem create table t1 as select trunc((rownum-1)/4) t1_n1, trunc((rownum-1)/4) t1_n2, case mod(rownum,20) when 0 then rownum else -1 end flagged1, rpad(rownum,180) t1_v1 from all_objects where rownum <= 3000 --> comment to avoid wordpress format issue ; create table t2 as select mod(rownum,200) t2_n1, mod(rownum,200) t2_n2, case mod(rownum,20) when 0 then rownum else -1 end flagged2, rpad(rownum,180) t2_v1 from all_objects where rownum <= 3000 --> comment to avoid wordpress format issue ; create table t3 as select trunc((rownum-1)/4) t3_n1, trunc((rownum-1)/4) t3_n2, case mod(rownum,20) when 0 then rownum else -1 end flagged3, rpad(rownum,180) t3_v1 from all_objects where rownum <= 3000 --> comment to avoid wordpress format issue ; create table t4 as select trunc((rownum-1)/4) t4_n1, trunc((rownum-1)/4) t4_n2, case mod(rownum,20) when 0 then rownum else -1 end flagged4, rpad(rownum,180) t4_v1 from all_objects where rownum <= 3000 --> comment to avoid wordpress format issue; create index t1_i1 on t1(t1_n1); create index t2_i1 on t2(t2_n1); create index t3_i1 on t3(t3_n1); create index t4_i1 on t4(t4_n1);
复制
然后,我们检查如下简单语句的执行计划,该语句看起来像一个命名查询块:
explain plan for select /*+ qb_name(main) */ * from t1, t2, t3, t4 where t2.t2_n1 = t1.t1_n2 and t3.t3_n1 = t2.t2_n2 and t4.t4_n1 = t3.t3_n2 ; select * from table(dbms_xplan.display(null,null,'outline alias'));
复制
Query Block Name / Object Alias (identified by operation id): ------------------------------------------------------------- 1 - MAIN 2 - MAIN / T4@MAIN 4 - MAIN / T3@MAIN 6 - MAIN / T2@MAIN 7 - MAIN / T1@MAIN Outline Data ------------- /*+ BEGIN_OUTLINE_DATA SWAP_JOIN_INPUTS(@"MAIN" "T4"@"MAIN") SWAP_JOIN_INPUTS(@"MAIN" "T3"@"MAIN") SWAP_JOIN_INPUTS(@"MAIN" "T2"@"MAIN") USE_HASH(@"MAIN" "T4"@"MAIN") USE_HASH(@"MAIN" "T3"@"MAIN") USE_HASH(@"MAIN" "T2"@"MAIN") LEADING(@"MAIN" "T1"@"MAIN" "T2"@"MAIN" "T3"@"MAIN" "T4"@"MAIN") FULL(@"MAIN" "T4"@"MAIN") FULL(@"MAIN" "T3"@"MAIN") FULL(@"MAIN" "T2"@"MAIN") FULL(@"MAIN" "T1"@"MAIN") OUTLINE_LEAF(@"MAIN") ALL_ROWS DB_VERSION('19.1.0') OPTIMIZER_FEATURES_ENABLE('19.1.0') IGNORE_OPTIM_EMBEDDED_HINTS END_OUTLINE_DATA */ Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("T4"."T4_N1"="T3"."T3_N2") 3 - access("T3"."T3_N1"="T2"."T2_N2") 5 - access("T2"."T2_N1"="T1"."T1_N2")
复制
请注意,在Query Block Name / Object Alias information信息中,所有4个表都是“源于”的,或被定义了由“@MAIN”限定的别名,在最终执行计划中,所有表都用于名为MAIN的查询块中。
现在,让我们看看等价的ANSI环境:
explain plan for select /*+ qb_name(main) */ * from t1 join t2 on t2.t2_n1 = t1.t1_n2 join t3 on t3.t3_n1 = t2.t2_n2 join t4 on t4.t4_n1 = t3.t3_n2 ; select * from table(dbms_xplan.display(null,null,'outline alias'));
复制
Query Block Name / Object Alias (identified by operation id): ------------------------------------------------------------- 1 - SEL$43767242 2 - SEL$43767242 / T4@SEL$3 4 - SEL$43767242 / T3@SEL$2 6 - SEL$43767242 / T2@SEL$1 7 - SEL$43767242 / T1@SEL$1 Outline Data ------------- /*+ BEGIN_OUTLINE_DATA SWAP_JOIN_INPUTS(@"SEL$43767242" "T4"@"SEL$3") SWAP_JOIN_INPUTS(@"SEL$43767242" "T3"@"SEL$2") SWAP_JOIN_INPUTS(@"SEL$43767242" "T2"@"SEL$1") USE_HASH(@"SEL$43767242" "T4"@"SEL$3") USE_HASH(@"SEL$43767242" "T3"@"SEL$2") USE_HASH(@"SEL$43767242" "T2"@"SEL$1") LEADING(@"SEL$43767242" "T1"@"SEL$1" "T2"@"SEL$1" "T3"@"SEL$2" "T4"@"SEL$3") FULL(@"SEL$43767242" "T4"@"SEL$3") FULL(@"SEL$43767242" "T3"@"SEL$2") FULL(@"SEL$43767242" "T2"@"SEL$1") FULL(@"SEL$43767242" "T1"@"SEL$1") OUTLINE(@"SEL$1") OUTLINE(@"SEL$2") MERGE(@"SEL$1" >"SEL$2") OUTLINE(@"SEL$58A6D7F6") OUTLINE(@"SEL$3") MERGE(@"SEL$58A6D7F6" >"SEL$3") OUTLINE(@"SEL$9E43CB6E") OUTLINE(@"MAIN") MERGE(@"SEL$9E43CB6E" >"MAIN") OUTLINE_LEAF(@"SEL$43767242") ALL_ROWS DB_VERSION('19.1.0') OPTIMIZER_FEATURES_ENABLE('19.1.0') IGNORE_OPTIM_EMBEDDED_HINTS END_OUTLINE_DATA */ Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("T4"."T4_N1"="T3"."T3_N2") 3 - access("T3"."T3_N1"="T2"."T2_N2") 5 - access("T2"."T2_N1"="T1"."T1_N2")
复制
查看Plan Hash Value,他给我们一个强有力的证据表明两种执行计划是相同的,仔细检查计划的主体和谓词信息可以确认这两个查询以完全相同的方式以及完全相同的成本运行。但是在查询块和表别名上有很大的区别。
Query Block Name / Alias Alias 信息告诉我们,查询块“main”已经消失,查询完全从内部生成名称为SEL$43767242的查询块中执行;此外,我们可以看到表t1和t2来自名为SEL$1的查询块,而t3来自SEL$2,t4来自SEL$3。
最后,下面是一个杂乱的Oracle形式,用于重现ANSI查询块名和表别名:
explain plan for select /*+ qb_name(main) */ * from ( select /*+ qb_name(sel$3) */ * from ( select /*+ qb_name(sel$2) */ * from ( select /*+ qb_name(sel$1) */ * from t1, t2 where t2.t2_n1 = t1.t1_n2 ) v1, t3 where t3.t3_n1 = v1.t2_n2 ) v2, t4 where t4.t4_n1 = v2.t3_n2 ); select * from table(dbms_xplan.display(null,null,'outline alias'));
复制
Query Block Name / Object Alias (identified by operation id): ------------------------------------------------------------- 1 - SEL$43767242 2 - SEL$43767242 / T4@SEL$3 4 - SEL$43767242 / T3@SEL$2 6 - SEL$43767242 / T2@SEL$1 7 - SEL$43767242 / T1@SEL$1 Outline Data ------------- /*+ BEGIN_OUTLINE_DATA SWAP_JOIN_INPUTS(@"SEL$43767242" "T4"@"SEL$3") SWAP_JOIN_INPUTS(@"SEL$43767242" "T3"@"SEL$2") SWAP_JOIN_INPUTS(@"SEL$43767242" "T2"@"SEL$1") USE_HASH(@"SEL$43767242" "T4"@"SEL$3") USE_HASH(@"SEL$43767242" "T3"@"SEL$2") USE_HASH(@"SEL$43767242" "T2"@"SEL$1") LEADING(@"SEL$43767242" "T1"@"SEL$1" "T2"@"SEL$1" "T3"@"SEL$2" "T4"@"SEL$3") FULL(@"SEL$43767242" "T4"@"SEL$3") FULL(@"SEL$43767242" "T3"@"SEL$2") FULL(@"SEL$43767242" "T2"@"SEL$1") FULL(@"SEL$43767242" "T1"@"SEL$1") OUTLINE(@"SEL$1") OUTLINE(@"SEL$2") MERGE(@"SEL$1" >"SEL$2") OUTLINE(@"SEL$58A6D7F6") OUTLINE(@"SEL$3") MERGE(@"SEL$58A6D7F6" >"SEL$3") OUTLINE(@"SEL$9E43CB6E") OUTLINE(@"MAIN") MERGE(@"SEL$9E43CB6E" >"MAIN") OUTLINE_LEAF(@"SEL$43767242") ALL_ROWS DB_VERSION('19.1.0') OPTIMIZER_FEATURES_ENABLE('19.1.0') IGNORE_OPTIM_EMBEDDED_HINTS END_OUTLINE_DATA */ Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("T4"."T4_N1"="T3"."T3_N2") 3 - access("T3"."T3_N1"="T2"."T2_N2") 5 - access("T2"."T2_N1"="T1"."T1_N2")
复制
再次快速的查看一下Plan Hash Value可以确认这个散乱的查询与之前做了ANSI转换的查询是匹配的。并且执行计划主体和Query Block Name / Object Alias信息也是确认匹配的。
任何编写ANSI语法时,这种嵌套内联视图的分层就是发生在应用任何其他转换之前—有时(尽管在Oracle的最新版本中非常罕见),这可能会导致优化器随后转换查询的方式受到意外的限制。
抛开上面提到的意外,“ANSI rewrite”最大的问题来自于所有额外查询块的副作用。除了最简单的情况外,如果您想应用HINT来解决优化器问题,您必须更加努力地找出需要使用的查询块名称—您不能为您编写的查询中的每个查询块都创建自己有意义的名称。幸运的是,如果您在hint中添加了/+no_query_transformation/之后查看相应的执行计划,那么这个任务会变得简单一些,因为这会告诉优化器生成一个计划,它看起来像是查询编写方式的逐步“转换”(当然,除了ANSI转换)。这可能足够标识优化器在使用ANSI语法时的基本的查询块。