设为首页 收藏本站
查看: 1551|回复: 0

[经验分享] Oracle SQL执行计划基线总结(SQL Plan Baseline)

[复制链接]

尚未签到

发表于 2018-10-19 08:22:37 | 显示全部楼层 |阅读模式
  一、基础概念
  Oracle 11g开始,提供了一种新的固定执行计划的方法,即SQL plan baseline,中文名SQL执行计划基线(简称基线),可以认为是OUTLINE(大纲)或者SQL PROFILE的改进版本,基本上它的主要作用可以归纳为如下两个:
  1、稳定给定SQL语句的执行计划,防止执行环境或对象统计信息等等因子的改变对SQL语句的执行计划产生影响!
  2、减少数据库中出现SQL语句性能退化的概率,理论上不允许一条语句切换到一个比已经执行过的执行计划慢很多的新的执行计划上!
  注意:
  1、从Oracle的发展角度来看,估计这种方法是Oracle发展和改进的方向,如今outline已经被废弃,sql profile估计在后续的发行版本中也难有改进,因此,对于从11g开始接触Oracle的朋友来说,一定要对sql计划基线有所了解,因为这是以后的主流!
  2、SQL执行计划基线保存在数据字典中,查询优化器会自动判断使用他们。
  二、工作机制
  从Oracle 11g开始,由于基线的存在,一条语句的解析过程大概如下:
  SQL语句被硬解析后,CBO(优化器)会产生很多个的执行计划,CBO从中选择一个成本最低执行计划。
  基于SQL语句的文本形成一个哈希值(signature),通过这个哈希值来检查数据字典中是否存在同样的基线。
  如果基线存在,优化器会对刚刚产生的执行计划和保存在SQL plan baseline中的执行计划进行比较。
  如果基线中有与CBO刚产生的执行计划的匹配的SQL执行计划存在,并且被标记为可接受(‘accepted’),则这个CBO生成的执行计划被启用。
  如果基线中没有匹配的SQ执行计划存在,CBO评估基线中被标记为‘accepted’的的多个执行计划,并选择其中cost最低的执行计划。(注意,一个语句的基线可以有多个执行计划被保存,这是与其他Outline和SQL profiel都不同的地方)
  如果刚刚硬解析过程中CBO选择的执行计划比保存在基线中的执行计划COST都低,这个新生成的执行计划被标记为‘not-accepted’并保存在基线中。直到这个执行计划被演化且验证后才会被考虑使用,即标记为accepted(演化和验证,可以简单理解为Oracle确认这个执行计划可以带来更好的性能)。
  Oracle 就是通过上面这种方式来确保SQL语句的性能不会退化(即第一部分中我归纳的第二个主要作用),称为“执行计划保守选择策略”
  三、基线的一些特点
  简单归纳如下几个
  通过OPTIMIZER_USE_SQL_PLAN_BASELINE来控制Oracle是否使用基线,默认值为TRUE,即会自动使用基线。
  11g中默认是不会自动创建基线
  与OUTLINE和SQL Profile不同,基线中不存在分类的概念
  与OUTLINE和SQL Profile不同,每个SQL语句可以有多个基线。Oracle根据制定的规则来判断具体是否哪个基线
  基线针对RAC中所有的实例都生效
  基线有两个表示,一个为sql_handle,可以理解为表示语句文本的唯一标识,一个为sql_plan_name可以理解为执行计划的唯一标识
  不能像sql profile一样通过force_matching属性将字面值不一样的SQL语句使用一个基线应用多个语句。
  三、创建基线的几种方式
  1、自动捕获基线,通过将optimizer_cature_sql_plan_baselines设置为true,优化器为重复执行两次以上的SQL语句生成并保存基线(可以系统级或会话级修改)
  2、从SQL调优集合中加载,通过使用包dbms_spm.load_plans_from_sqlset来从SQL调优集合中加载基线
  DECLARE
  l_plans_loaded  PLS_INTEGER;
  BEGIN
  l_plans_loaded := DBMS_SPM.load_plans_from_sqlset(
  sqlset_name => 'my_sqlset');
  END;
  /
  3、从库缓存中加载,通过包dbms_spm.load_plans_from_cursor_cache函数为一条已经在游标缓存中的语句创建基线
  DECLARE
  l_plans_loaded  PLS_INTEGER;
  BEGIN
  l_plans_loaded := DBMS_SPM.load_plans_from_cursor_cache(sql_id => '1fkh93md0802n',plan_hash_value=>null);
  END;
  /
  备注:可以有多种方式加载,例如可以根据sql文本进行模糊匹配、SQL语句解析的用户名等等方式,具体见文档
  四、基线的几种状态
  一个SQL语句对应的基线,我将它们归纳为三种状态
  accepted(可接受),只有这种状态的基线,优化器才会考虑此基线中的执行计划
  no-accepted(不可接受),这种状态的基线,优化器在SQL语句解析期间不会考虑。这种状态的基线必须通过演化和验证通过后,转变为accepted状态后,才会被优化器考虑使用
  fixed为yes(固定),这种状态的基线固有最高优先级!比其他两类基线都要优先考虑
  五、查看基线
  1、基本视图:dba_sql_plan_baselines、dba_sql_management_config
  2、底层视图:sqlobj$data 、 sqlobj$  (保存具体的hint),如下查看基线中保存的执行计划语句:
  select
  extractvalue(value(d), '/hint') as outline_hints
  from
  xmltable('/outline_data/hint'
  passing (
  select
  xmltype(comp_data) as xmlval
  from
  sqlobj$data sod, sqlobj$ so
  where so.signature = sod.signature
  and so.plan_id = sod.plan_id
  and comp_data is not null
  and name like '&baseline_plan_name'
  )
  ) d;
  3、通过函数来查看基线的详细信息:
  select * from table(dbms_xplan.display_sql_plan_baseline(sql_handle=>'SYS_SQL_11bcd50cd51504e9',plan_name=>'SQL_PLAN_13g6p1maja1790cce5f0e'));
  六、演化基线
  为了验证基线中一个处于不可接受状态的执行计划是否比一个处于可接受状态的执行计划具有更高的效率,必须通过演化来验证,需要让优化器以不同的执行计划来执行这条SQL语句,观察不可接受状态的执行计划基线是否会带来更好的性能,如果性能确实更高,这个不可接受状态的基线将会转换为可接受状态。演化的方式有两种:
  1、手工执行运行
  SELECT DBMS_SPM.evolve_sql_plan_baseline(sql_handle => 'SYS_SQL_xxxxxxxxxxxxx') From dual;
  还有time_limit/verify/commit几个参数,可以参考文档
  2、调优包实现基线的自动演化,可以理解为,启动一个调度任务,周期性的检查是否有不可接受状态的基线可以被演化
  七、修改基线
  可以通过dbms_spm.alter_sql_plan_baseline包来修改基线的一些属性,主要有如下几个属性
  ENABLED :设置该属性的值为NO告诉Oracle 11g临时禁用某个计划,一个SQL计划必须同时标记为ENABLED和ACCEPTED,否则CBO将忽略它
  FIXED:设置为YES,那个计划将是优化器唯一的选择[最高优先级],即使如果某个计划可能拥有更低的成本。这让DBA可以撤销SMB的默认行为,对于转换一个存储概要进入一稳定的SQL计划基线特别有用,注意当一个新计划被添加到被标记为FIXED的SQL计划基线,该新计划不能被利用除非它申明为FIXED状态
  AUTOPURG:设置这个属性的值为NO告诉Oracle 11g无限期保留它,从而不用担心SMB的自动清除机制
  plan_name : 改变SQL plan 名字
  description : 改变SQL plan描述
  语法:
  SET SERVEROUTPUT ON
  DECLARE
  v_text  PLS_INTEGER;
  BEGIN
  v_text  := DBMS_SPM.alter_sql_plan_baseline(sql_handle  => 'SYS_SQL_xxxxxx',plan_name => 'SYS_SQL_PLAN_xxxxxxxxx',
  attribute_name  => 'fixed',attribute_value => 'YES');

  DBMS_OUTPUT.put_line('Plans>  END;
  /
  八、迁移基线
  dbms_spm提供了多个过程来在数据库之间迁移SQL计划基线
  create_stgtab_baseline创建一个计划基线保存表
  pack_stgtab_baseline将基线从数据字典复制到第一步的表中
  unpack_stgtab_baseline将基线从保存表中复制到迁移数据库的数据字典中
  大概过程如下:
  1、创建一张保存数据字典中基线表内容的用户表
  exec dbms_spm.create_stgtab_baseline(table_name => 'BASELINE_TEST',table_owner => 'SCOTT',tablespace_name =>'');
  2、将数据字典中基线表的内容 插入到 第一步创建的用户表中
  exec :i := dbms_spm.pack_stgtab_baseline(table_name => 'BASELINE_TEST', table_owner => 'SCOTT');
  备注:可以支持多种方式插入,例如包含特定字符的SQL相关的基线,sql_handle来精确识别一个基线,具体见文档
  3、通过迁移工具迁移用户表
  exp/imp or expdp/impdp
  4、将迁移过来的用户表中保存的基线内容 插入到当前库的数据字典中,从而实现迁移
  exec :i := dbms_spm.unpack_stgtab_baseline(table_name => 'BASELINE_TEST',table_owner => 'SCOTT');
  备注:可以支持多种方式,与步骤2一样,具体见文档
  九、删除基线
  可以通过dbms_SPM.drop_sql_plan_baseline包来手工删除数据字典里的基线
  为使用的基线,fixed为no的基线,将在一定的保留期后自动删除(可查看dba_sql_management_config视图)
  手工删除方法如下
  SET SERVEROUTPUT ON
  DECLARE
  v_text  PLS_INTEGER;
  BEGIN
  v_text := DBMS_SPM.drop_sql_plan_baseline(sql_handle => 'SYS_SQL_7b76323ad90440b9',plan_name  => NULL);
  DBMS_OUTPUT.put_line(v_text);
  END;
  /
  十、将一个SQL语句固定为我们期望的执行计划
  我一般通过如下几步实现(仅供参考)
  1、为这个SQL语句创建基线
  2、给这个SQL语句添加hint赖宇星,确保SQL语句添加hint后的执行计划与我们期望一样
  3、将第2步产生的执行计划,添加到第一步创建的基线中(注意,前面已经说过,一个SQL语句可以有多个基线!)
  4、删除基线中第1步创建的那个执行计划(这样,我们就可以确保基线中只有我们期望的执行计划,即保存第2步SQL语句的执行计划)
  5、验证是否生效
  后续有示例,加深理解!
  十一、示例(将一个SQL语句固定为我们期望的执行计划)
  首先运行两个结构相同的语句,下面的实验通过SQL计划基线,将一个语句的执行计划通过另一个语句的执行计划来固定
  SQL> select sql_handle,plan_name,dbms_lob.substr(sql_text,60,1) sql_text,ACCEPTED from dba_sql_plan_baselines;
  未选定行

  SQL>>  系统已更改。

  SQL> select / outlinetest2 / /+ full(dh_stat) / * from dh_stat where>  ID NAME                           TYPE
  

   711 I_STREAMS_PROCESS_PARAMS1      INDEX  


  SQL> select / outlinetest3 / /+ index(dh_stat) / * from dh_stat where>  ID NAME                           TYPE
  

   711 I_STREAMS_PROCESS_PARAMS1      INDEX  

  SQL> select sql_text,sql_id,hash_value,child_number,plan_hash_value,to_char(LAST_ACTIVE_TIME,'hh24:mi:ss') time
  2   from v$sql a where sql_text like '%outlinetest%' and sql_text not like '%v$sql%';
  SQL_TEXT                                                SQL_ID        HASH_VALUE CHILD_NUMBER PLAN_HASH_VALUE TIME
  select / outlinetest2 / /+ full(dh_stat) / from d 4vaj9fgjysy9c 3823925548            0      1845196118 12:27:31

  h_stat where>  select / outlinetest3 / /+ index(dh_stat) / from  fm35jcmypb3qu 4250242778            0      2780970545 12:27:41

  dh_stat where>
SQL> select * from table(dbms_xplan.display_cursor('4vaj9fgjysy9c','',''));
  PLAN_TABLE_OUTPUT

SQL_ID  4vaj9fgjysy9c, child number 0

select / outlinetest2 / /+ full(dh_stat) / * from dh_stat where
  id=711
  Plan hash value: 1845196118

|>
|   0 | SELECT STATEMENT  |         |       |       |   124 (100)|          |
  |*  1 |  TABLE ACCESS FULL| DH_STAT |     1 |    38 |   124   (1)| 00:00:02 |

Predicate Information (identified by operation>  1 - filter("ID"=711)
  已选择19行。

SQL>  select * from table(dbms_xplan.display_cursor('fm35jcmypb3qu','',''));
  PLAN_TABLE_OUTPUT

SQL_ID  fm35jcmypb3qu, child number 0

select / outlinetest3 / /+ index(dh_stat) / * from dh_stat where
  id=711
  Plan hash value: 2780970545

|>
|   0 | SELECT STATEMENT            |         |       |       |     2 (100)|          |
  |   1 |  TABLE ACCESS BY INDEX ROWID| DH_STAT |     1 |    38 |     2   (0)| 00:00:01 |
  |*  2 |   INDEX RANGE SCAN          | IND_1   |     1 |       |     1   (0)| 00:00:01 |

Predicate Information (identified by operation>  2 - access("ID"=711)
  已选择20行。
  SQL> DECLARE
  2   k1 pls_integer;
  3  begin
  4  k1 := DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE (
  5  sql_id=>'4vaj9fgjysy9c',
  6  plan_hash_value=>1845196118
  7  );
  8  end;
  9  /
  PL/SQL 过程已成功完成。
  SQL> select sql_handle,plan_name,dbms_lob.substr(sql_text,60,1) sql_text,ACCEPTED from dba_sql_plan_baselines;
  SQL_HANDLE                     PLAN_NAME                      SQL_TEXT                                        ACC
  SYS_SQL_11bcd50cd51504e9       SQL_PLAN_13g6p1maja17934f41c8d select / outlinetest2 / /+ full(dh_stat) / * from d YES
  h_sta
  刚生产sql plan baseline的时候,第一次查询,无法找到执行计划,直到第二次执行的时候,才能看到,如下

  SQL> select / outlinetest2 / /+ full(dh_stat) / * from dh_stat where>  ID NAME                           TYPE
  

   711 I_STREAMS_PROCESS_PARAMS1      INDEX  

SQL> select * from table(dbms_xplan.display_cursor('4vaj9fgjysy9c','',''));
  PLAN_TABLE_OUTPUT

  SQL_ID: 4vaj9fgjysy9c cannot be found
  SQL> select sql_text,sql_id,hash_value,child_number,plan_hash_value,to_char(LAST_ACTIVE_TIME,'hh24:mi:ss') time
  2   from v$sql a where sql_text like '%outlinetest%' and sql_text not like '%v$sql%';
  SQL_TEXT                                                SQL_ID        HASH_VALUE CHILD_NUMBER PLAN_HASH_VALUE TIME
  select / outlinetest3 / /+ index(dh_stat) / * from  fm35jcmypb3qu 4250242778            0      2780970545 12:27:41

  dh_stat where>
  SQL> select / outlinetest2 / /+ full(dh_stat) / * from dh_stat where>  ID NAME                           TYPE
  

   711 I_STREAMS_PROCESS_PARAMS1      INDEX  

  SQL> select sql_text,sql_id,hash_value,child_number,plan_hash_value,to_char(LAST_ACTIVE_TIME,'hh24:mi:ss') time
  2   from v$sql a where sql_text like '%outlinetest%' and sql_text not like '%v$sql%';
  SQL_TEXT                                                SQL_ID        HASH_VALUE CHILD_NUMBER PLAN_HASH_VALUE TIME
  select / outlinetest2 / /+ full(dh_stat) / from d 4vaj9fgjysy9c 3823925548            0      1845196118 12:30:54

  h_stat where>  select / outlinetest3 / /+ index(dh_stat) / from  fm35jcmypb3qu 4250242778            0      2780970545 12:27:41

  dh_stat where>
SQL> select * from table(dbms_xplan.display_cursor('4vaj9fgjysy9c','',''));
  PLAN_TABLE_OUTPUT

SQL_ID  4vaj9fgjysy9c, child number 0
  select / outlinetest2 / /+ full(dh_stat) / * from dh_stat where
  id=711

Plan hash value: 1845196118

|>
|   0 | SELECT STATEMENT  |         |       |       |   124 (100)|          |
  |*  1 |  TABLE ACCESS FULL| DH_STAT |     1 |    38 |   124   (1)| 00:00:02 |

Predicate Information (identified by operation>
1 - filter("ID"=711)
  Note


  • SQL plan baseline SQL_PLAN_13g6p1maja17934f41c8d used for this statement  已选择23行。

  将符合我们预期的执行计划的加载到第一次生成的sql baseline中!
  SQL> DECLARE
  2   k1 pls_integer;
  3  begin
  4  k1 := DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE (
  5  sql_id=>'fm35jcmypb3qu',
  6  plan_hash_value=>2780970545,sql_handle=>'SYS_SQL_11bcd50cd51504e9'
  7  );
  8  end;
  9  /
  PL/SQL 过程已成功完成。
  可以看到,SYS_SQL_11bcd50cd51504e9下目前有两个plan_name
  SQL> select sql_handle,plan_name,dbms_lob.substr(sql_text,60,1) sql_text,ACCEPTED from dba_sql_plan_baselines;
  SQL_HANDLE                     PLAN_NAME                      SQL_TEXT                                        ACC
  SYS_SQL_11bcd50cd51504e9       SQL_PLAN_13g6p1maja1790cce5f0e select / outlinetest2 / /+ full(dh_stat) / * from d YES
  h_sta
  SYS_SQL_11bcd50cd51504e9       SQL_PLAN_13g6p1maja17934f41c8d select / outlinetest2 / /+ full(dh_stat) / * from d YES
  h_sta
  删除第一个plan_name,即将我们不需要的执行计划版本去除掉!
  SQL> DECLARE
  2   k1 pls_integer;
  3  begin
  4  k1 := DBMS_SPM.drop_sql_plan_baseline (  sql_handle=>'SYS_SQL_11bcd50cd51504e9',plan_name=>'SQL_PLAN_13g6p1maja17934f41c8d');
  5  end;
  6  /
  PL/SQL 过程已成功完成。
  通过下面的一部分测试,我们可以看到,新的SQL计划基线已经正常生效,及时语句中包含full提示,执行计划也走索引定位数据

  SQL> select / outlinetest2 / /+ full(dh_stat) / * from dh_stat where>  ID NAME                           TYPE
  

   711 I_STREAMS_PROCESS_PARAMS1      INDEX  

SQL> select * from table(dbms_xplan.display_cursor('4vaj9fgjysy9c','',''));
  PLAN_TABLE_OUTPUT

SQL_ID  4vaj9fgjysy9c, child number 1

select / outlinetest2 / /+ full(dh_stat) / * from dh_stat where
  id=711
  Plan hash value: 2780970545

|>
|   0 | SELECT STATEMENT            |         |       |       |     2 (100)|          |
  |   1 |  TABLE ACCESS BY INDEX ROWID| DH_STAT |     1 |    38 |     2   (0)| 00:00:01 |
  |*  2 |   INDEX RANGE SCAN          | IND_1   |     1 |       |     1   (0)| 00:00:01 |

Predicate Information (identified by operation>
2 - access("ID"=711)
  Note


  • SQL plan baseline SQL_PLAN_13g6p1maja1790cce5f0e used for this statement  已选择24行。

  可以通过dba_sql_plan_baselines来显示可用的SQL计划基线的一般信息,也可以通过如下这种方式显示执行SQL计划基线的详细信息!
  select * from table(dbms_xplan.display_sql_plan_baseline(sql_handle=>'SYS_SQL_11bcd50cd51504e9',plan_name=>'SQL_PLAN_13g6p1maja1790cce5f0e'));

PLAN_TABLE_OUTPUT

SQL handle: SYS_SQL_11bcd50cd51504e9
  SQL text: select / outlinetest2 / /+ full(dh_stat) / * from dh_stat where
  id=711

Plan name: SQL_PLAN_13g6p1maja1790cce5f0e         Plan>  Enabled: YES     Fixed: NO      Accepted: YES     Origin: MANUAL-LOAD

  Plan hash value: 2780970545

|>
|   0 | SELECT STATEMENT            |         |     1 |    38 |     2   (0)| 00:00:01 |
  |   1 |  TABLE ACCESS BY INDEX ROWID| DH_STAT |     1 |    38 |     2   (0)| 00:00:01 |
  |*  2 |   INDEX RANGE SCAN          | IND_1   |     1 |       |     1   (0)| 00:00:01 |

Predicate Information (identified by operation>  2 - access("ID"=711)
  已选择26行。
  查看SQL计划基线中保存的hint提示集合
  SQL> conn /as sysdba
  已连接。
  SQL> select
  2  extractvalue(value(d), '/hint') as outline_hints
  3  from
  4  xmltable('/outline_data/hint'
  5  passing (
  6  select
  7  xmltype(comp_data) as xmlval
  8  from
  9  sqlobj$data sod, sqlobj$ so
  10  where so.signature = sod.signature
  11  and so.plan_id = sod.plan_id
  12  and comp_data is not null
  13  and name like '&baseline_plan_name'
  14  )
  15  ) d;
  输入 baseline_plan_name 的值:  SQL_PLAN_13g6p1maja1790cce5f0e
  原值   13: and name like '&baseline_plan_name'
  新值   13: and name like 'SQL_PLAN_13g6p1maja1790cce5f0e'

OUTLINE_HINTS
  IGNORE_OPTIM_EMBEDDED_HINTSOPTIMIZER_FEATURES_ENABLE('11.2.0.1')
  DB_VERSION('11.2.0.1')
  ALL_ROWS
  OUTLINE_LEAF(@" sel>br/>OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
  DB_VERSION('11.2.0.1')
  ALL_ROWS
  OUTLINE_LEAF(@"SEL$1")
INDEX_RS_ASC(@"SEL$1" "DH_STAT"@"SEL$1" ("DH_STAT"."ID"))
  已选择6行。



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-623384-1-1.html 上篇帖子: Server-U的使用 下篇帖子: SQL语句中事务与try catch使用
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表