暂无图片
暂无图片
5
暂无图片
暂无图片
暂无图片

使用SPM来稳定执行计划

原创 杜伟 2024-05-27
2182

使用SPM来稳定执行计划

        Oracle在11G中推出了SPM(SQL Plan management),SPM是一种主动的稳定执行计划的手段,能够保证只有被验证过的执行计划才会被启用,

当由于种种原因(比如统计信息的变更)而导致目标SQL产生了新的执行计划后,这个新的执行计划并不会被马上启用,直到它已经被我们验证过其执行

效率会比原先执行计划高才会被启用。

        当启用了SPM后,每一个SQL都会存在对应的SQL plan baseline,这个SQL plan baseline里存储的就是该SQL的执行计划,如果一个SQL有多个执行

计划,那么该SQL就可能会有多个SQL plan baseline,你可以从DBA_SQL_PLAN_BASELINES中查看目标SQL所有的SQL plan baseline。


        SQL plan baseline中的列ENABLED和ACCEPTED用来描述一个SQL plan baseline所对应的执行计划是否被oracle启用,只有enanled和accepted的值均

为yes的SQL plan baseline所对应的执行计划才会被oracle启用,如果一个SQL有超过1个以上的SQL plan baseline的enabled和accepted的值均为yes,

则oracle会从中选择成本值最小的一个所对应的执行计划来作为该SQL的执行计划。

在oracle 11g及其以上的版本中,有如下两张方法可以产生目标SQL的SQL plan baseline。
1、自动捕获
2、手工生成/批量导入(批量导入尤其适用于oracle数据库大版本的升级,它可以确保升级后原有系统所有SQL的执行计划不会发生变更)

一)自动捕获SQL plan baseline的方式。


        参数OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES用于控制是否开启自动捕获SQL plan baseline,其默认值为false,表示在默认情况下,oracle并

不会自动捕获SQL plan baseline。这个参数是可以在session何系统级别动态修改的,当将其值修改为true后,则oracle会对上述参数影响的范围内所有重复

执行的SQL自动捕获其SQL plan baseline,并且针对目标SQL第一次捕获的SQL plan baseline的enabled和accepted的值均为yes。随后如果该sql的执行计

划发生了变更,则再次捕获到的SQL plan baseline的enabled值依然为yes,但accepted的值变为了No,这表示后续变更的执行计划虽然被捕获了,但是

oracle不会将其作为该SQL的执行计划来执行,即此时oracle会永远沿用该SQL第一次被捕获的SQL plan baseline所对应的执行计划(除非后续做手动调整)。 

参数OPTIMIZER_USE_SQL_PLAN_BASELINES用于控制是否启用SQL plan baseline,其默认值为true,表示在默认情况下,oracle在生产执行计划时就会启

用SPM,使用已有的SQL plan baseline,这个参数也可以在session和系统级别动态调整。

        自动捕获SQL plan baseline稳定执行计划示例:
oracle 11G中OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES的默认值为false,OPTIMIZER_USE_SQL_PLAN_BASELINES的默认值为TRUE。


查看当前数据库默认情况:
show parameter OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES
show parameter OPTIMIZER_USE_SQL_PLAN_BASELINES


修改上面两个参数:
alter session set OPTIMIZER_USE_SQL_PLAN_BASELINES=false;
alter session set OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES=true;


创建测试表
create table dw1019 as select * from dba_objects;
create index idx_dw1019 on dw1019(object_id);


收集统计信息:
exec dbms_stats.gather_table_stats(ownname=>'SCOTT',TABNAME=>'DW1019',ESTIMATE_PERCENT=>100,CASCADE=>TRUE)
select object_id,object_name from dw1019 where object_id between 103 and 108;
select * from table(dbms_xplan.display_cursor(null,null,'advanced'));


        通过执行计划可以看出,目标SQL的执行计划走的是对索引范围扫描。因为目标SQL只执行了一次,所以oracle现在不会自动捕获其SQL plan baseline。从如

下查询结果中可以看出,该SQL现在确实没有对应的SQL plan baseline。


select sql_handle,plan_name,origin,enabled,accepted,sql_text from dba_sql_plan_baselines where sql_text like 'select object_id,%';


        再次执行查询,并查看执行计划

select object_id,object_name from dw1019 where object_id between 103 and 108;


因为目标SQL已重复执行,所以现在oracle就会自动捕获其SQL plan baseline了。从如下查询结果中可以看出,oracle已经针对上述执行计划(即对索引

idx_dw1019的索引范围扫描)产生了一个SQL plan baseline,其enabled何accepted的值均为yes。


这里我们将索引inx_dw1019的聚簇因子修改为2400万,目前是为了能让目标SQL的执行计划变为对表dw1019的全表扫描。
exec dbms_stats.set_index_stats(ownname =>'SCOTT',indname=>'IDX_DW1019',clstfct=>24000000,no_invalidate=> false);
查看聚簇因子确实修改成2400万了: select index_name,clustering_factor from dba_indexes where index_name='IDX_DW1019';


        重新执行目标SQL并查看执行计划,发现执行计划由原来索引范围扫描变成了全表扫描。因为目标SQL已经重复执行且同时又产生了一个新的执行计划,

所以现在oracle就会自动捕获并创建这个新的执行计划所对应的SQL plan baseline了。从如下查询结果中可以看出,oracle已经针对上述执行计划(即全表扫描)

产生了一个新的SQL plan baseline,其enabled的值yes,但accepted的值变为了no。




        现在索引IDX_DW1019的聚簇因子依然是2400万,执行sql查看执行计划,发现还是走索引范围扫描,注意到这次执行计划中的note部分有如下内容 

SQL plan baseline SQL_PLAN_dk871rk5wfjhff7864b0e used for this statement ,这表明在SPM已开启的情况下,即使目标SQL产生了新的执行计划,oracle依

然只会应用该SQL的enabled和accepted的值均为yes的SQL plan baseline。

可以查询select sql_handle,plan_name,origin,enabled,accepted,sql_text from dba_sql_plan_baselines where sql_text like 'select object_id,%';来查看这两个

参数的值。


        从上述测试结果中可以很清晰地看到,SPM确实能够稳定目标SQL的执行计划。当启用了SPM后,它确实能够保证只有被验证过的执行计划

(即enabled和accepted的值均为yes的SQL plan baseline所对应的执行计划)才会被启用,当由于某种原因(比如统计信息变更)而导致目标SQL产生了新的执行

计划后,这个新的执行计划并不会被oracle启用。


        如果想启用目标SQL新的执行计划(即对表进行全表扫描),应该如何做呢?针对不同的oracle版本,会有不同的处理方法。比如这里想启用目标SQL进行全表

扫描的话,如果是11gR1的环境,则值需将目标SQL所采用的名为SQL_PLAN_dk871rk5wfjhff7864b0e的SQL plan baseline(即对索引IDX_DW1019的索引范围所

对应的SQL plan baseline)的ACCEPTED值设为NO就可以了。但遗憾的是,上述SQL的执行环境是ORACLE 11G R2,所以以如下方式执行

DBMS_SPM.ALTER_SQL_PLAN_BASELINE时oracle会报错(因为在oracle 11gR2中,所有已经被ACCEPTED的SQL plan baseline的ACCEPTED值将不再能够被设为NO);

在ORACLE 11G R2中,我们可以联合使用DBMS_SPM.EVOLVE_SQL_BASELINE和DBMS_SPM.ALTER_SQL_PLAN_BASELINE达到启用目标SQL新的执行计划的目的。


        先使用DBMS_SPM.EVOLVE_SQL_BASELINE将目标SQL新的执行计划(全表扫描)所对应的名为SQL_PLAN_dk871rk5wfjhf30e445f2的SQL plan baseline的

ACCEPTED的值设为yes,


exec :temp:=dbms_spm.evolve_sql_plan_baseline(sql_handle =>'SQL_d920e1bc8bc7460e',plan_name =>'SQL_PLAN_dk871rk5wfjhf30e445f2',verify=>'NO',commit =>'YES');
查询发现名为SQL_PLAN_dk871rk5wfjhf30e445f2的SQL plan baseline的ACCEPTED的值设为yes。然后再使用DBMS_SPM.ALTER_SQL_PLAN_BASELINE将原先的执行计划所对应的

SQL plan baseline的enabled的值设为NO;
exec :temp:=dbms_spm.alter_sql_plan_baseline(sql_handle =>'SQL_d920e1bc8bc7460e',plan_name =>'SQL_PLAN_dk871rk5wfjhff7864b0e',attribute_name =>'ENABLED',attribute_value =>'NO');

查询可看到所有范围扫描的SQL plan baseline的enabled的值设为NO,再次值查询语句sql再查看执行计划,从执行计划内容可以看出,现在SQL的执行计划已经从对索引的范围扫描,

变成了全表扫描,即要启用新的执行计划的目的已经实现。
从上述测试结果可以看出,实际上我们可以轻易地在目标SQL的多个执行计划中切换,所有SPM确实是既能够主动地执行计划,又保留了继续使用新的执行计划的机会,并且我们很容易就能启用新的执行计划。


接下来介绍手工生产SQL plan baseline。SQL plan baseline的手工生产。


步骤如下:
1)针对目标SQL使用DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE手工生产其初始执行计划所对应的SQL plan baseline。此时,使用DBMS_SPM.LOAD_PLANS_FROM_CURSO_CACHE

传入的参数为如下所示:

dbms_spm.load_plans_from_cursor_cache(sql_id =>'原目标SQL的sql_id',plan_hash_value =>原目标SQL的plan hash value)

2)改写原目标SQL的sql文本,在其中加入核实的hint,直到加入HINT后的所改写的SQL能走出想要的执行计划,然后对改写后的SQL使用dbms_spm.load_plans_from_cursor_cache

手工生产新的执行计划所对应的SQL plan baseline。此时,使用DBMS_SPM.LOAD_PLANS_FROM_CURSO_CACHE传入的参数为如下所示:

dbms_spm.load_plans_from_cursor_cache(sql_id =>'加入核实hint改写sql的sql_id',plan_hash_value =>'加入hint的sql的plan hash value',sql_handle =>'原目标SQL在步骤1中所产生的SQL handle');

3)使用dbms_spm.drop_sql_plan_baseline删除在步骤1中手工生成的SQL plan baseline。示例如下:
dbms_spm.drop_sql_plan_baseline(sql_handle =>'步骤1中的',plan_name =>'步骤1中的');

具体操作步骤示例(数据库SPM默认设置):
a、 select /*+ no_index(t idx_dw1019) */ object_name,object_id from dw1019 t where object_id=4;
b、查看执行计划 上面sql是走的全表扫描
c、select sql_handle,plan_name,origin,enabled,accepted,sql_text from dba_sql_plan_baselines where sql_text like '%from dw1019 t where object_id=4%';查询没有结果
d、手动生成spm
var temp number;
exec :temp:=dbms_spm.load_plans_from_cursor_cache(sql_id =>'fw8hr4x4aa9h8',plan_hash_value =>1103564333);
e、查询目标sql的SQL plan baseline已生成并记录下sql_handle及plan_name
select sql_handle,plan_name,origin,enabled,accepted,sql_text from dba_sql_plan_baselines where sql_text like '%from dw1019 t where object_id=4%';
f、改写sql加上适当的hint
select /*+ index(t idx_dw1019) */ object_name,object_id from dw1019 t where object_id=4;
g、用上面改写的sql_id及plan_hash_value加入到目标SQL的SQL plan baseline中
exec :temp:=dbms_spm.load_plans_from_cursor_cache(sql_id =>'bwdw4qm9qfmr9',plan_hash_value =>'1907077966',sql_handle =>'SQL_62dd675eafd1bf34');
h、drop掉原执行计划所对应的SQL plan baseline
exec :temp:=dbms_spm.drop_sql_plan_baseline(sql_handle =>'SQL_62dd675eafd1bf34',plan_name =>'SQL_PLAN_65rb7burx3gtn30e445f2');
i、验证脚本是否走想要的执行计划
select /*+ no_index(t idx_dw1019) */ object_name,object_id from dw1019 t where object_id=4;
查看真实的执行计划,发现走的是索引范围扫描,手动绑定执行计划成功。



本文参考了崔华老师的《基于oracle的sql优化》,加上自己的试验。!!!










































「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论