基于Oracle的SQL优化(崔华著)-整理笔记-第2章“Oracle里的执行计划”
详细介绍了Oracle数据里与执行计划有关的各个方面的内容,包括执行计划的含义,加何查看执行计划,如何得到目标SQL真实的执行计划,如何查看执行计划的执行顺序,Oracle数据库里各种常见的执行计划的含义,以及如何在Oracle数据库中稳定执行计划。
2.1什么是执行计划
为了执行SQL语句,Oracle在内部必须实现许多步骤,这些步骤可能是从数据库中物理检索数据行,或者用某种方法来准备数据行等,接着Oracle会按照一定的顺序依次执行这些步骤,最后将其执行结果作为目标SQL的最终执行结果返回给用户。Oracle用来执行目标SQL语句的这些步骤的组合就被称为执行计划。
我们来看_AI Oracle数据库中执行计划的实例 。
上述执行计划是在执行目标SQL (select/++real_exp_examplel */tl.coll,tl.c012,t2.c013 from tl,t2 where tl.c012=t2.c012)后使用DBMS_XPLAN包中的方法DISPLAY_CURSOR得到的,这是目标SQL的真实执行计划。
从显示结果中我们可以看到,上述SQL的执行计划可以分为如下三个部分。
1.目标SQL的正文、SQL_ID和其执行计划所对应的PLAN HASH VALUE
可以着到该SQL的
SQL ID |
|
6fc6zasdtltr7 |
|
|
child number |
|
0 |
|
|
SQL正文 |
|
“select/4+real_exp_examplel */tl.coll,tl.c012,t2.c013 from tl,t2 where tl.c012=t2.c012” |
|
|
执行计划所对应的Plan Hash Value |
|
282751716 |
|
|
2.执行计划的主体部分
从其中我们可以看到Oracle在执行目标SQL时所用的内部执行步骤,这些步骤的执行顺序,所对应的谓
词信息、列信息,优化器评估出来执行这些步骤后返回结果集的Cardinality、成本等内容。
我们可以看到,Oracle在执行目标SQL时使用了对表Tl和T2的哈希连接,CBO评估出来上述哈希连接
后返回结果集的Cardinality(ROWS)的值是4,成本值(COST)为31。
执行计划中的“Query Block Name”和“Outline Data”部分是CBO在执行SQL时所用到的Query Block的名称和用于固定执行计划的内部Hint组合。实际上你可以将“Outline Data”部分的内容摘出来加到目标SQL中以固定其执行计划,即可以将目标SQL改写成如下的形式:
select /*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE ( '10 . 2 . O . 5 ' )
ALL_ROWS
OUTLIbfE_LEAF (@"SEL$1")
FULL(@"SEL$1" "T2"@"SEL$l")
FULL (@"SEL$1" "Tl"@"SEL$l")
LEADING (@' SEL$l" "T2"@"SEL$l" "Tl"@"SEL$l")
USE_HASH(@"SEL$1" "Tl"@"SEL$l")
END_OUTLINE_DATA
*/
tl.coll, tl.c012, t2.c013
from tl, t2
where tl.c012 - t2.c012
洼意,在上述执行计划中id 为1的执行步骤“HASH JOIN”前有一个星号(Id列的数字l前有一个“+”),
表示该执行步骤有对应的驱动或者过滤查询条件,这个星号所对应的具体的驱动或过滤查询条件可以从执行计
划的“Predicate Information (identified by operatlon id)”中找到。实际上,这部分的内容就是上述执行步骤所对
应的谓词信息:
Predicate Information (identified by operation id):
1 - access("T1"."COL2”= "T2"."COLZ")
我们可以看到,“access("T1"."COL2”= "T2"."COLZ")”前的数字也是l,这里关键字“access”表示驱动查
词条件,这就表明"T1"."COL2”= "T2"."COLZ"就是上述执行计划中Id为1的执行步骤“HASH JOIN”所对应的
驱动查询条件。
3.执行计划的额外补充信息
从其中我们可以看到Oracle在执行目标SQL时有没有使用一些额外的技术手段,比如是否使用了动态采
样(dynamic sampling),是否使用Cardinality Feedback(Oracle llg中引入的修正执行计划中返回结果集的
Cardinality的一种技术下段),是否使用SQL Profile(Oracle lOg中引入的调整、稳定执行计划的一种方法)等。
这部分额外的补充信息可以从执行计划中的“Note”部分看到。本例的执行计划中“Note”部分的内容如
下所示:
Note
- - - - -
- dynamic sampling used for this statement
我们可以看到Oracle在执行目标SQL时使用了动态采样。
以下是使用Cardinality Feedback后执行计划中的“Note”部分显示的内容:
2.2如何查看执行计划
在Oracle数据库里,我们通常可以使用如下这些方法(包括但不限于)得到目标SQL的执行计划:
(1) explain plan命令
(2) DBMS_XPLAN包
(3) SQLPLUS中的AUTOTRACE开关
(4) 10046事件
(5) 10053事件
(6) AWR报告或Statspack报告
(7)一些现成的脚本(如display_cursor_9i.sql等)
2.2.1 PLSQL中的 F5键 只是对 explain 、select * from table(dbms_xplan)的一层封装
2.2.2 DBMS_XPLAN包
使用DBMS XPLAN包中的方法是在Oracle数据库中得到目标SQL的执行计划的第二种方法。针对不同
的应用场景,你可以选择如下四种方法中的一种:
1. select * from table (dbms_xplan.display);
2. select * from table (dbms_xplan.display_cursor(null,null, 'advanced')); 方法2用于在SQL_PLUS中查看刚刚执行过的SQL的执行计划(太被动了)。这里针对方法DBMS_XPLAN.DISPLAY_CURSOR所传入的第一个和第二个参数的值均为null,第三个参数的值是“advanced”,第三个输入参数的值也可以是“all”,只不过用“advanced”后的显示结果会 比用“all”的显示结果更详细一些。 在SQL执行完毕后紧跟着执行“ select * from table(dbms_xplan.display_cursor(null,null,'advanced' ))”,就可以查看该SQL的实际执行计划了: |
3. select * from table (dbms_xplan.display_cursor('sql_id/hash_value', child_cursor_number,'advanced')); 方法3用于查看指定SQL的执行计划。 这里针对方法DBMS__ XPLAN.DISPLAY_CURSOR所传入的第一个参数的值是指定SQL的SQL ID或者SQL HASH VALUE,第二个参数的值是要查看的执行计划所在的 Child Cursor Number,第三个参数的含义在介绍方法2时已经提过,这个参数的值我们一般都用“advanced”,因为这样展现出来的执行计划是最详细的。
本质上SQL ID和SQL HASH VALUE是一回事,它们是可以互相转换的,这也是方法 BMS_XPLAN.DISPLAY CURSOR所传入的第一个参数的值可以是SQL ID,也可以是SQL HASH VALUE SQL> select lower(trim(13yfu3wh150aqt!))sql id, trunc(mod (sum((instr(10123456789abedfghikmnpqrstuvwxyz' niintgsett)),level,1))-1)*power(32,1ength(trin( 3yfuswhis0asth nnntsubstr(lower(trim('3yfu3whis0aqt')),level,1))-1)'pove nint-level)),power(2,32)))hash value from dual SeleCt*fromtable(dbms_xplan·display--Cursor('3yfu3whl50aqt',0,'adVanCed')); PLANTABLEOU丁PUT 3Q乙_zD3yfu3材址50aqt,c城ldn皿bero 日。l.ctempno,.name,dnamefrom,cott.emp,3cott.d.ptwhere e爪p.deptno=dept.deptno Planha,hvalue:8咬4388907 工d operation N已垃e RO材, Byte, Co,t(协CPU)1Ti花e 11111 000OD ....…… 00000 00000 ....…… 00000 00000 飞.夕飞.了飞.了飞.了飞.夕飞.了 070080 0户吸、J‘、了‘、夕.、矛吸、 1 才.飞 662143 1111 O}3ELECT3T人TE加ENT 11MERGEJOIN 2}TABLEACCE33BYINDEX 3}INDEXFULLSCAN 吸!gORTJOIN 5}TASLEACCE33FULL D名PT pKDEPT 378 52 EHP 196 196 44444 111 去 山口口口口口口口口口口口口口口口口口口口口口口口口口口口自口口口口口 QueryBlockName/objeot人工ia二(identifiedbyoperationid): 1一3EL车1 2一3EL车1/DEPT日SEL与1 3一3EL$1/DEPT日SEL$1 5一SEL导1/包MPe3ELSI OutlineData" width="576" height="315">
|
4. select * from table (dbms_xplan.display_awr('sql_id')); 方法4用于查看指定SQL的所有历史执行计划。
使用方法2、3能够显示目标SQL执行计划的前提条件是该SQL的执行计划还在Shared Pool中,而如果该SQL的执行计划已经被age out出Shared Pool,那么只要该SQL的执行计划被Oracle采集到AWR Repository中,我们就可以使用方法4来查看该SQL的所有历史执行计划。 我们来看一个用方法4来查看指定SQL所有历史执行计划的实例。从如下查询结果可以看出,目标SQL现在的SQL_ID是“79glp919t7x4u”。注意,该SQL对应的列VERSION COUNT的值为2,这说明该SQL有两个Child Cursor,即该SQL很可能有两个不同的执行计划。
SQL> select * from table (dbms_xplan.display_cursor ( ' 79glp919t7x4u' , 1, 'advanced' J ) ;
PLAN TABLE_OUTPUT --------------------------------------------------------------- soL_ID: 7991p919t7x4u, child number: 1 cannot be found
由于之前我们已经通过手工采集AWR报告的方式将目标SQL的执行计划采集到了AWR Repository中,所以现在我们可以通过执行方法4(即执行DBMS XPLAN.DISPLAY AWR)来得到该SQL的所有历史执行计划 用 DBMS_ XPLAN.DISPLAY_AWR和之前用 DBMS_XPLAN.DISPLAY__ CURSOR显示的执行计划相比,有一个非常不好的地方一一就是用 DBMS__ XPLAN.DISPLAY_AWR显示的执行计划中看不到执行步骤对应的谓词条件,我个人认为这很糟糕!因为谓词条件对于我们理解执行计划,特别是理解复杂执行计划是不可或缺的。这里的根本原因是Oracle在把执行计划的采样数据从V$SQL_PLAN挪到AWR Repository的基表WRH$_SQL_PLAN中时没有保留V$SQL_PLAN中记录谓词条件(包括驱动查询条件和过滤查询条件)的列ACCESS—PREDICATES和FILTER PREDICATES的值,所以不是DBMS XPLAN.DISPLAY__ CURSOR不想显示谓词条件,而是根本就没有谓词条件可供其显示。 |
2,2.4 1 0046事件与tkprof命令
使用10046事件是在Oracle数据库中查看目标SQL的执行计划的另一种方法。这种方法与使用explain plan命令、DBMS_XPLAN包和AUTOTRACE开关的不同之处在于,所得到的执行计划中明确显示了目标SQL实际执行计划中每一个执行步骤所消耗的逻辑读、物理读和花费的时间。这种细粒度的明细显示在我们诊断复杂SQL的性能问题时尤为有用,而且这也是其他三种方法所不能提供的(实际上,用
GATHER_PLAN_STATISTICS Hint配合DBMS_XPLAN包一起使用也可以达到类似10046事件这种绌粒度的明细显示效果,对于GATHER_PLAN_STATISTICS Hint的用法,会在“第6章Oracle里的Hint”中详细说明,这里不再赘述)。
用10046事件得到目标SQL的执行计划是很容易的,只需要依次执行如下三个步骤:
·首先在当前Session中激活10046事件: ·接着在此Session中执行目标SQL; ·最后在此Session中关闭10046事件。 |
当执行完上述步骤后,Oracle就会将目标SQL的执行计划和明细资源消耗写入此Session所对应的trace文件中,查看这个trace文件就能知道目标SQL的执行计划和资源消耗明细了。Oracle会在参数
USER。DUMP—DEST所代表的目录下生成这个trace文件,其命名格式为“实例名ora当前Session的spid.trc”,例如ipratest_ora_856454.trc。
通常可以使用如下这两种方法在当前Session中激活10046事件:
·在当前session中执行alter session set events '10046 trace name context forever,level 12
·在当前session中执行oradebug event 10046 trace name context forever,level 12
上述命令中的关键字“level”后的数字是表示设置的10046事件的level值。这个值是可以修改的,我们通常使用的值为12,表示在产生的trace文件中除了有目标SQL的执行计划和资源消耗明细之外,还会包含目标SQL所使用的绑定变量的值以及陔Session所经历的等待事件。除了上述level值之外,其他的部分都是固定的语法,我们无法修改。
2.3如何得到真实的执行计划
在2.2节中介绍了在Oracle数据库里通常可以使用如下这四种方法来得到目标SQL的执行计划:
‘explain plan命令
·DBMS_ XPLAN包
·SQLPLUS中的AUTOTRACE开关
·10046事件
这其中除了第四种方法之外,其他三种方法得到的执行计划都有可能是不准确的。在Oracle数据库中判断得到的执行计划是否准确,就是看目标SQL是否被真正执行,真正执行过的SQL所对应的执行计划就是准的,反之则有可能不准。注意,这里的判断原则从严格意义上来说并不适用于AUTOTRACE开关,因为所有使用AUTOTRACE开关所显示的执行计划都有可能是不准的,即使对应的目标SQL实际上已经执行过,这一点在后面会详细解释。
下画我们就用上述原则来判断除第4种以外的其他三种方法中哪些方法得到的执行计划是准的,哪些方法得到的执行计划有可能不准。
对使用第一种方法(即使用explain plan命令)得到的执行计划而言,因为此时目标SQL并没有被实际执行,所以用该方法得到的执行计划有可能是不准的,尤其是在目标SQL包含绑定变量时。在默认丌启绑定变量窥探(Bind Peeking)的情况下,对含绑定变量的目标SQL使用explain plan得到的执行计划只是一个半成品,Oracle在随后对该SQL的绑定变量进行窥探后就得到了这些绑定变量具体的值,此时Oracle很可能会对上述半成品的执行计划做调整,一旦做了调整,使用explain plan命令得到的执行计划就不准了(关于绑定变量和绑定变量窥探,会在“第3章Oracle里的Cursor和绑定变量”中详细介绍,这里不再赘述)。
对使用第二种方法(即使用DBMS__ XPLAN包)而言,针对不同的应用场景,你可以选择如下四种方式中的一种:
select * from table (dbms_xplan.display);
select * from table(dbms_xplan.display_cursor(null, null, ‘advanced'));
select * from table (dbms_xplan.display_cursor(’sql_id/hash_value’, child_cursor_number,’advanced'));
select * from table (dbms_xplan.display_awr(’sql_id‘));
显然,执行select} from table(dbms_xplan.display)所得到的执行计划可能是不准的,因为它只是用于查看使用explain plan命令得到的目标SQL的执行计划,目标SQL此时还没有被真正执行,所以用它得到的执行计划可能是不准的。使用利下的三种方式所得到的执行计划都是准的,因为此时目标SQL都已经被实际执行过了。
对使用第三种方法(即使用SQLPLUS中的AUTOTRACE开关)而言,你可以选择执行如下三种方式中的一种来开启AUTOTRACE开关:
SET AUTOTRACE ON(可以简写为SET AUTOT ON).
SET AUTOTRACE TRACEONLY(可以简写为SET AUTOT TRACE):
SET AUTOTRACE TRACEONLY EXPLAIN(可以简写为SET AUTOT TRACE EXP); (慎用,因为select操作并没有被执行、DML语句才被真正执行了 )
上述三种方式中,当使用SET AUTOTRACE ON和SET AUTOTRACE TRACEONLY时,目标SQL都已经被实际执行过了,正是因为被实际执行过,所以在SET AUTOTRACE ON和SET AUTOTRACE TRACEONLY的情况下我们能看到目标SQL的实际资源消耗情况。当使用SET AUTOTRACE TRACEONLY EXPLAIN时,如果执行的是SELECT语句,则该SELECT语句并没有被Oracle实际执行,但如果执行的是DML语句,情况就不一样了,此时的DML语句是会被Oracle实际执行的。
soL> set autotrace traceonly explain soL> select count(*) from emp where ename='CUIHUAl'; 执行计划
煞后再次查询V$SQLAREA: soL> selec七 sql_text, executions from v$sqlarea where sql_text like ' select count (*) from empoo ';
SQL TEXT EXECUTIONS ------------------------------------------------------------ ------------------------------------------------ select count(*) from emp where ename=,CUIHUAll' O
从上述查询结果中可以看到该SELECT语句所对应的EXECUTIONS的值为O,这说明Oracle刚才确实只是解析了该SELECT句但并没有实际执行它。即我们之前的论点(当使用SET AUTOTRACE TRACEONLY EXPLAIN时,如果执行的是SELECT语句,则该SELECT语句并没有被Oracle实际执行)得到了证实。
虽然使用部分SET AUTOTRACE命令后目标SQL实际上已经执行过了,但所 有使用SET AUTOTRACE命令(包括SET AUTOTRACE ON、SET AUTOTRACE TRACEONLY、SET AUTOTRACE TRACEONLY EXPLAIN)断得到的执行计划都有可能是不准的,因为使用SET AUTOTRACE 命令所显示的执行计划都是来源于调用explain plan命令。
请看以下case
|