hr @ora10g > create table t1 as select object_id as id, object_name from dba_objects; hr @ora10g > update t1 set id = 1 where rownum <= 10000; hr @ora10g > commit; hr @ora10g > create index idx_t1 on t1(id);
这样,该表里id为的1记录有一万条,而id为其它值的记录都仅仅有一条。从而,我们构建出一个分布不均匀的測试用表。然后,我们收集一下统计信息。注意,这里要收集直方图,为的是要让CBO知道id列上的数据分布不均匀。
hr @ora10g > begin 2 dbms_stats.gather_table_stats( 3 user, 4 ' t1 ', 5 cascade => true, 6 method_opt => ' for columns id size 254 ' 7 ); 8 end; 9 /
我们找到表t1里最大的id,然后以该id作为第一个绑定变量传入,能够想象,该绑定变量将导致走索引。注意,我们这里设定的优化器目标为all_rows。
hr @ora11g > select max(id) from t1; MAX(ID) -- -------- 13871 hr @ora10g > alter system flush shared_pool; hr @ora10g > var v_id number; hr @ora10g > var v_sql_id varchar2( 20); hr @ora10g > exec :v_id : = 13871; hr @ora10g > select * from t1 where id =:v_id; 此处省略查询结果 hr @ora10g > begin 2 select sql_id into :v_sql_id from v$sql 3 where sql_text like ' select * from t1 where id=:v_id% '; 4 end; 5 / hr @ora10g > select * from table(dbms_xplan.display_cursor(:v_sql_id)); PLAN_TABLE_OUTPUT -- ------------------------------------------------------------------------------ SQL_ID djwq30cpbcz7k, child number 0 -- ----------------------------------- select * from t1 where id =:v_id Plan hash value: 50753647 -- ------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost ( %CPU) | Time -- ------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | | | 11 ( 100) | | 1 | TABLE ACCESS BY INDEX ROWID | T1 | 1365 | 28665 | 11 ( 0) | 00: 00: 01 |* 2 | INDEX RANGE SCAN | IDX_T1 | 1365 | | 3 ( 0) | 00: 00: 01 -- ------------------------------------------------------------------------------ ...... hr @ora10g > exec :v_id : = 1; hr @ora10g > select * from t1 where id =:v_id; 此处省略查询结果 hr @ora10g > begin 2 select sql_id into :v_sql_id from v$sql 3 where sql_text like ' select * from t1 where id=:v_id% '; 4 end; 5 / hr @ora10g > select * from table(dbms_xplan.display_cursor(:v_sql_id)); PLAN_TABLE_OUTPUT -- ------------------------------------------------------------------------------ SQL_ID djwq30cpbcz7k, child number 0 -- ----------------------------------- select * from t1 where id =:v_id Plan hash value: 50753647 -- ------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost ( %CPU) | Time -- ------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | | | 11 ( 100) | | 1 | TABLE ACCESS BY INDEX ROWID | T1 | 1365 | 28665 | 11 ( 0) | 00: 00: 01 |* 2 | INDEX RANGE SCAN | IDX_T1 | 1365 | | 3 ( 0) | 00: 00: 01 -- ------------------------------------------------------------------------------
从上面结果能够看出,在为绑定变量传入第一个值为13871时,因为返回的记录条数较少,导致走索引扫描。当我们第二次传入绑定变量值1时,oracle不再生成新的运行计划,而直接拿索引扫描的运行路径来用。
可是,假设先传入1的绑定变量值,然后再传入13871的绑定变量值时,会如何?我们继续測试。
hr @ora10g > alter system flush shared_pool; hr @ora10g > set autotrace traceonly exp stat; hr @ora10g > exec :v_id : = 1; hr @ora10g > select * from t1 where id =:v_id; hr @ora10g > begin 2 select sql_id into :v_sql_id from v$sql 3 where sql_text like ' select * from t1 where id=:v_id% '; 4 end; 5 / hr @ora10g > select * from table(dbms_xplan.display_cursor(:v_sql_id)); PLAN_TABLE_OUTPUT -- ------------------------------------------------------------------------------ SQL_ID djwq30cpbcz7k, child number 0 -- ----------------------------------- select * from t1 where id =:v_id Plan hash value: 3617692013 -- ------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost ( %CPU) | Time | -- ------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | | | 13 ( 100) | | |* 1 | TABLE ACCESS FULL | T1 | 8738 | 179K | 13 ( 0) | 00: 00: 01 | -- ------------------------------------------------------------------------ ...... hr @ora10g > exec :v_id : = 13871; hr @ora10g > select * from t1 where id =:v_id; hr @ora10g > begin 2 select sql_id into :v_sql_id from v$sql 3 where sql_text like ' select * from t1 where id=:v_id% '; 4 end; 5 / hr @ora10g > select * from table(dbms_xplan.display_cursor(:v_sql_id)); PLAN_TABLE_OUTPUT -- ------------------------------------------------------------------------------ SQL_ID djwq30cpbcz7k, child number 0 -- ----------------------------------- select * from t1 where id =:v_id Plan hash value: 3617692013 -- ------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost ( %CPU) | Time | -- ------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | | | 13 ( 100) | | |* 1 | TABLE ACCESS FULL | T1 | 8738 | 179K | 13 ( 0) | 00: 00: 01 | -- ------------------------------------------------------------------------
非常明显,先传入1的绑定变量时将导致生成的运行计划走全表扫描。后面传入的13871的绑定变量的最佳运行路径应该是索引扫描,可是因为CBO并不知道这一点,而是直接拿第一次生成的运行计划来用了,于是也走全表扫描了。
2. 11g之后的动态绑定变量窥视
而从11g開始,这个尴尬的问题開始得到了改善。因此从11g開始,引入了所谓的自适应游标共享(Adaptive Cursor Sharing)。该特性是一个很复杂的技术,用来平衡游标共享和SQL优化这两个矛盾的目标。11g里不会盲目的共享游标,而是会去查看每一个绑定变量,并为不同的绑定变量来产生不同的运行计划。而oracle这么做的前提是,使用多个运行计划的所带来的收益,要比产生多个运行计划所引起的CPU开销要更大。
使用自适应游标共享时,会遵循以下的步骤:
1) 一条新的SQL语句第一次传入shared pool时,还是和曾经一样,进行硬解析。并且进行绑定变量窥视,计算where条件各个列的selectivity,同一时候假设绑定变量所在的列上存在直方图的话,也会去參考该直方图来计算selectivity。该游标会被标记为是一个绑定敏感的游标(bind-sensitive cursor)。同一时候,oracle还会保留包括绑定变量的where条件的其它信息,比方selectivity等。Oracle会为该谓词的selectivity维持一个范围,oracle叫做立方体(cube)。仅仅要传入的绑定变量所产生的selectivity落在该范围里面,也就是落在该cube里面,就不产生新的运行计划,而直接拿该cube所相应的运行计划来用。
2) 下次再次运行同样的SQL时,传入了新的绑定变量,如果使用新的绑定变量的谓词的selectivity落在已经存在的cube范围里,于是这次SQL的运行会使用该cube所相应的运行计划。
3) 同样的查询再次运行时,如果所使用的新的绑定变量导致这时候的selectivity不再落在已经存在的cube里了,于是也就找不到相应的运行计划。于是系统会进行一个硬解析,这将产生第二个新的运行计划。并且新的selectivity以及相应的cube也会保存下来。也就是说,这时,我们分别有两个cube以及两个运行计划。
4) 同样的查询再次运行时,如果所使用的新的绑定变量导致这时候的selectivity不落在现存的两个cube中的不论什么一个,所以系统又会进行硬解析。如果这时硬解析所产生的运行计划与第一次产生运行计划一样,也就是说,在第一次评估selectivity的cube时过于保守,导致cube过小,进而导致了这一次的不必要的硬解析。于是,oracle会将第一次产生的cube与这次产生的cube合并成一个新的更大的cube。那么,下次再次进行软解析的时候,如果selectivity落在新的cube里,则会使用第一次所产生的运行计划。
我们从这里能够看到,11g对这个问题的处理很精彩。这样做的结果是,系统開始执行时,CPU消耗可能会比較严重,可是随着系统不断执行,cube的不断合并从而不断扩大,于是系统的CPU消耗会不断下降,同一时候执行计划也会更加的合理。
我们来做个试验进行验证。我们採用11g新引入的运行计划管理特性来验证该特性。
与10g中的測试一样,创建一个数据分布不均匀的表,在数据分布不均匀的列上创建索引,并收集统计信息,收集时注意要收集直方图,从而让CBO知道该列上的数据分布不均匀。
hr @ora11g > create table t1 as select object_id as id, object_name from dba_objects; hr @ora11g > select count( *) from t1; COUNT( *) -- -------- 12064 hr @ora11g > update t1 set id = 1 where rownum <= 10000; hr @ora11g > commit; hr @ora11g > create index idx_t1 on t1(id); hr @ora11g > begin 2 dbms_stats.gather_table_stats( 3 user, 4 ' t1 ', 5 cascade => true, 6 method_opt => ' for columns id size 254 ' 7 ); 8 end; 9 /
我们找到表t1里最大的id,然后以该id作为第一个绑定变量传入,能够想象,该绑定变量将导致走索引。
hr @ora11g > select max(id) from t1; MAX(ID) -- -------- 12462 我们将optimizer_capture_plan_baselines设置为true,从而让oracle自己主动获取plan baseline。 hr @ora11g > alter system set OPTIMIZER_CAPTURE_PLAN_BASELINES =true; hr @ora11g > alter system flush shared_pool; hr @ora11g > var v_id number; hr @ora11g > exec :v_id : = 12462; hr @ora11g > select * from t1 where id =:v_id; hr @ora11g > select * from t1 where id =:v_id;
我们运行两遍select * from t1 where id=:v_id,从而让oracle捕获plan baseline。我们知道id为12462的记录仅仅有一条,因此该SQL应该使用索引扫描。然后我们再为绑定变量传入1,我们知道id为1的记录有一万条,所以较好的运行计划不应该走已经生成的运行计划,而应该走全表扫描。
hr @ora11g > exec :v_id : = 1; hr @ora11g > set autotrace traceonly stat; -- 之所以设置stat是为了让该sql实际运行,但不要返回全部记录, hr @ora11g > select * from t1 where id =:v_id; hr @ora11g > select sql_handle,plan_name,origin,enabled,accepted 2 from dba_sql_plan_baselines where sql_text like ' select * from t1% '; SQL_HANDLE PLAN_NAME ORIGIN ENA ACC -- --------------------- ----------------------------- -------------- --- --- SYS_SQL_ea05bbed6f2f670c SYS_SQL_PLAN_6f2f670c844cb98a AUTO -CAPTURE YES YES SYS_SQL_ea05bbed6f2f670c SYS_SQL_PLAN_6f2f670cdbd90e8e AUTO -CAPTURE YES NO
我们能够发现,如今该SQL语句存在两个运行计划了,当中第一个运行计划,也就是accepted为YES的运行计划为v_id等于12462得到的,而第二个运行计划,也就是accepted为NO的是由v_id等于1得到的。第二个运行计划还没有被增加plan baseline,所以优化器不会使用该运行计划。我们将第二个运行计划的accepted改为YES,从而让oracle考虑使用该计划。
hr @ora11g > var cnt number; hr @ora11g > begin 2 :cnt : = dbms_spm.alter_sql_plan_baseline( 3 sql_handle => ' SYS_SQL_ea05bbed6f2f670c ', 4 plan_name => ' SYS_SQL_PLAN_6f2f670cdbd90e8e ', 5 attribute_name => ' ACCEPTED ', attribute_value => ' YES '); 6 end; 7 /
我们来看一下这两个运行计划各自是如何的。
注意:在这里我们要验证oracle会为不同绑定变量生成不同的运行计划时,不能使用set autotrace traceonly exp stat等其它方式。由于set autotrace得出的运行计划始终都是第一次生成的运行计划。我们通过plan baseline从側面来验证它。当然,我们也能够通过设置sql_trace=true从而将运行计划转储出来进行验证。
SQL > select * from table(dbms_xplan.display_sql_plan_baseline 2 ( ' SYS_SQL_ea05bbed6f2f670c ', ' SYS_SQL_PLAN_6f2f670c844cb98a ')); ...... -- ------------------------------------------------------------------------------ Plan hash value: 50753647 -- ------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost ( %CPU) | Time | -- ------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 6 | 126 | 2 ( 0) | 00: 00: 01 | | 1 | TABLE ACCESS BY INDEX ROWID | T1 | 6 | 126 | 2 ( 0) | 00: 00: 01 | |* 2 | INDEX RANGE SCAN | IDX_T1 | 6 | | 1 ( 0) | 00: 00: 01 | -- --------------------------------------------------------------------------------- ...... SQL > select * from table(dbms_xplan.display_sql_plan_baseline 2 ( ' SYS_SQL_ea05bbed6f2f670c ', ' SYS_SQL_PLAN_6f2f670cdbd90e8e ')); ...... -- ------------------------------------------------------------------------------ Plan hash value: 3617692013 -- ------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost ( %CPU) | Time | -- ------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 6 | 126 | 16 ( 0) | 00: 00: 01 | |* 1 | TABLE ACCESS FULL | T1 | 6 | 126 | 16 ( 0) | 00: 00: 01 | -- ------------------------------------------------------------------------ ......
非常明显,第一个是索引扫描,第二个是全表扫描。相同,我们来看一下v$sql里该sql语句有几条记录。
hr @ora11g > select sql_text,sql_id,child_number,plan_hash_value 2 from v$sql where sql_text like ' select * from t1 where% '; SQL_TEXT SQL_ID CHILD_NUMBER PLAN_HASH_VALUE -- ------------------------------- ------------- ------------ ---------------- select * from t1 where id =:v_id 7y7tt6xyhas1g 0 50753647
能够看到,该SQL语句眼下在内存里仅仅存在一个运行计划,其plan hash value就等于我们在前面plan baseline里看到的第一个走索引的运行计划的hash value。我们把该运行计划显示出来进行确认。
hr @ora11g > select * from table(dbms_xplan.display_cursor( ' 7y7tt6xyhas1g ', 0)); PLAN_TABLE_OUTPUT -- ------------------------------------------------------------------------------ SQL_ID 7y7tt6xyhas1g, child number 0 -- ----------------------------------- select * from t1 where id =:v_id Plan hash value: 50753647 -- ------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost ( %CPU) | Time | | 0 | SELECT STATEMENT | | | | 2 ( 100) | | | 1 | TABLE ACCESS BY INDEX ROWID | T1 | 1 | 21 | 2 ( 0) | 00: 00: 01 | |* 2 | INDEX RANGE SCAN | IDX_T1 | 1 | | 1 ( 0) | 00: 00: 01 | ......
结果非常明显,正是走索引的运行计划。然后我们继续为帮定变量传入1,多运行几次。
hr@ora11g > exec :v_id := 1;
hr@ora11g > set autotrace traceonly stat;
hr@ora11g > select * from t1 where id=:v_id;
hr@ora11g > select * from t1 where id=:v_id;
hr@ora11g > select * from t1 where id=:v_id;
注意:这里,我们之所以要多运行几次,主要是由于假设仅仅是运行一次或两次,oracle可以认识到你传入的绑定变量落在了第一次的绑定变量(12462)所在的cube之外,可是oracle觉得你可能仅仅是偶尔运行该绑定变量,所以并不一定会使用另外那个全表扫描的运行计划。多运行几次以后,你会发现consistent gets突然从1390直线下降到了715,这时就说明oracle開始使用新的全表扫描的运行计划了。
然后,这时我们再去查看v$sql里该sql语句有几条记录。
hr @ora11g > select sql_text,sql_id,child_number,plan_hash_value 2 from v$sql where sql_text like ' select * from t1 where% '; SQL_TEXT SQL_ID CHILD_NUMBER PLAN_HASH_VALUE -- ------------------------------- ------------- ------------ ---------------- select * from t1 where id =:v_id 7y7tt6xyhas1g 0 50753647 select * from t1 where id =:v_id 7y7tt6xyhas1g 1 3617692013
我们发现,该SQL语句在内存里存在两条记录了,也就是存在两个子游标了,分别相应了不同的运行计划。相同,我们来看一下新产生的子游标,也就是child_number为1的运行计划是如何的。
SQL> select * from table(dbms_xplan.display_cursor('7y7tt6xyhas1g',1));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID 7y7tt6xyhas1g, child number 1
-------------------------------------
select * from t1 where id=:v_id
Plan hash value: 3617692013
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 16 (100) | |
|* 1 | TABLE ACCESS FULL| T1 | 9974 | 204K | 16 (0) | 00:00:01 |
......
我们还能够从另外的角度来验证11g里的动态绑定变量窥视,也就是设置sql_trace的方式。这个方式比較简单,仅仅要先发出:alter session set sql_trace=true以后,传入两个不同的绑定变量,然后分别就不同的绑定变量多运行几次。最后调用tkprof对跟踪文件进行分析。这里注意两个地方,第一是跟踪文件位于ADR中,不再位于user_dump_dest參数所指定的文件夹里了。就这里的跟踪文件而言,其所在位置缺省为:$ORACLE_HOME/diag/rdbms/<DB name>/<SID>/trace文件夹下;第二个要注意的是使用tkprof时,加入aggregate=no选项,缺省会将同样SQL语句合并,这样你就发现不到对于同样SQL语句的不同的运行计划了。
这里节选部分使用tkprof得到的文件内容,例如以下所看到的。
...... SQL ID : 7y7tt6xyhas1g select * from t1 where id =:v_id ...... Rows Row Source Operation -- ----- --------------------------------------------------- 10000 TABLE ACCESS BY INDEX ROWID T1 (cr = 1390 pr = 0 pw = 0 time = 446 us cost = 2 size = 21 card = 1) 10000 INDEX RANGE SCAN IDX_T1 (cr = 687 pr = 0 pw = 0 time = 228 us cost = 1 size = 0 card = 1)(object id 12463) ...... SQL ID : 7y7tt6xyhas1g select * from t1 where id =:v_id ...... Rows Row Source Operation -- ----- --------------------------------------------------- 10000 TABLE ACCESS FULL T1 (cr = 715 pr = 0 pw = 0 time = 142 us cost = 16 size = 209454 card = 9974) ......
从这里也可以非常清楚的看到,对于不同的绑定变量,oracle可以自行选择是否应该生成更好的运行计划并使用该运行计划。
来自网络:http://tech.it168.com/db/2007-09-24/200709241709921_1.shtml