OBA技能1-获取执行计划
OBA技能2-执行计划顺序
OBA技能3-执行计划顺序表连接
ODBA 技能4实战执行计划
ODBA 技能5 固定执行计划
因为每次统计信息作业在收集完信息后,会触发ACS自适应游标管理程序,进行对绑定变量的窥探工作,窥探完可能就进行硬解析生成新的执行计划。为此一个SQL语句因为不可描述的原因导致自己的执行计划变多了,有的新计划的成本更低了,自然选择新计划。或者原来成本低的计划变得不可用了,只好选择次级计划。总之最终可能导致新选的计划,ORACLE认为好,实际执行更慢。
为此11G后出来个SPM和执行计划基线BASELINE。请不要被BASELINE给误导!真实行为含义就是执行计划库。SPM就是管理基库的一套程序。
一、基础概念
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确认这个执行计划可以带来更好的性能)。
上面讲解的工作机制有点绕,我们白话翻译一下。
1 假如你开启了SQL的执行计划库。
2 如果1个SQL语句发生硬解析,产生新的执行计划。然后去基库里找,看是否有已经存在的执行计划?
3 如果存在就使用该计划
4 如果不存在,但是还存在其他的执行计划,那么选择COST最低那个执行。
5 如果这个新生成的计划比库里的成本都低,它就被纳入库中并标记待检验。
这里有两个疑问,谁去验证?是不是每次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'1fkh93md0802n',plan_hash_value=>
四、基库的几种状态
一个SQL语句对应的基线,我将它们归纳为三种状态
accepted(可接受),只有这种状态的基库,优化器才会考虑此基库中的执行计划
no-accepted(不可接受),这种状态的基库,优化器在SQL语句解析期间不会考虑。这种状态的基库必须通过演化和验证通过后,转变为accepted状态后,才会被优化器考虑使用
fixed为yes(固定),这种状态的基库固有最高优先级!比其他两类基库都要优先考虑
五、查看基库
1、基本视图:dba_sql_plan_baselines、dba_sql_management_config
2、通过函数来查看基线的详细信息:
select *
from table(
dbms_xplan.display_sql_plan_baseline
(
sql_handle=>'SYS_SQL_11bcd50cd51504e9',
plan_name=>'SQL_PLAN_13g6p1maja1790cce5f0e'
)
);
下面显示具体的执行计划信息:
--------------------------------------------------------------------------------
SQL handle: SQL_44c9b37ac97e85e9
SQL text: select * from test_objects
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Plan name: SQL_PLAN_49kdmgb4rx1g95cd5cc6d Plan id: 1557515373
Enabled: YES Fixed: NO Accepted: YES Origin: MANUAL-LOAD
--------------------------------------------------------------------------------
Plan hash value: 3570092908
----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 68108 | 6518K| 275 (1)| 00:00:04 |
| 1 | TABLE ACCESS FULL | TEST_OBJECTS | 68108 | 6518K| 275 (1)| 00:00:04 |
----------------------------------------------------------------------------------
显然这有点不方便
六、演化基库
为了验证基库中一个处于不可接受状态的执行计划是否比一个处于可接受状态的执行计划具有更高的效率,必须通过演化来验证,需要让优化器以不同的执行计划来执行这条SQL语句,观察不可接受状态的执行计划基库是否会带来更好的性能,如果性能确实更高,这个不可接受状态的基库将会转换为可接受状态。演化的方式有两种:
1、手工执行运行
SELECT DBMS_SPM.evolve_sql_plan_baseline(sql_handle => From dual;
-------------------------------------------------------------------------------
Evolve SQL Plan Baseline Report
-------------------------------------------------------------------------------
Inputs:
-------
SQL_HANDLE = SQL_44c9b37ac97e85e9
PLAN_NAME =
TIME_LIMIT = DBMS_SPM.AUTO_LIMIT
VERIFY = YES
COMMIT = YES
-------------------------------------------------------------------------------
Report Summary
-------------------------------------------------------------------------------
There were no SQL plan baselines that required processing.
还有time_limit/verify/commit几个参数,可以参考文档
2、调优包实现基线的自动演化,可以理解为,启动一个调度任务,周期性的检查是否有不可接受状态的基线库可以被演化
手动
select DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE(sql_handle => NULL, plan_name => NULL, verify => 'YES',
commit => 'NO' ) from dual;
这里由两个标记控制:
o Verify
+ YES (只有性能更好的计划才会被演化)
+ NO (演化所有的计划)
o Commit
+ YES (直接演化)
+ NO (只生成报告)
不让系统智能选择使用新计划
七、修改基库
可以通过dbms_spm.alter_sql_plan_baseline包来修改基库的一些属性,主要有如下几个属性
ENABLED :设置该属性的值为NO告诉Oracle 11g临时禁用某个计划,一个SQL计划必须同时标记为ENABLED和ACCEPTED,否则CBO将忽略它
FIXED:设置为YES,那个计划将是优化器唯一的选择,即使如果某个计划可能拥有更低的成本。这让DBA可以撤销SPM的默认行为,对于转换一个存储概要进入一稳定的SQL计划基库特别有用,注意当一个新计划被添加到被标记为FIXED的SQL计划基库,该新计划不能被利用除非它申明为FIXED状态
AUTOPURG:设置这个属性的值为NO告诉Oracle 11g无限期保留它,从而不用担心SMB的自动清除机制
plan_name : 改变SQL plan 名字
description : 改变SQL plan描述
实验
1 开启SCOTT用户
2 创建表
create table test_objects as select * from all_objects
3 执行SQL语句
select * from test_objects
4 使用SYS等其他高级用户获得刚才的语句SQLID
select * from v$sqlarea s where sql_text order
5 SYS创建该SQL的基库
SQL> DECLARE
2 my_plans pls_integer;
3 BEGIN
4 my_plans := DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE (sql_id => '0g9hjnj0x1nbb');
5 END;
6 /
PL/SQL procedure successfully completed
6 查看执行计划基库
SELECT *
FROM dba_sql_plan_baselines
2 修改已有的Baseline
/*********语法
DBMS_SPM.ALTER_SQL_PLAN_BASELINE (
sql_handle IN VARCHAR2 := NULL,
plan_name IN VARCHAR2 := NULL,
attribute_name IN VARCHAR2,
attribute_value IN VARCHAR2 )
RETURN PLS_INTEGER;
************/
SET SERVEROUTPUT ON
DECLARE
l_plans_altered PLS_INTEGER;
BEGIN
l_plans_altered := DBMS_SPM.alter_sql_plan_baseline(
sql_handle => 'SQL_44c9b37ac97e85e9',
plan_name => 'SQL_PLAN_49kdmgb4rx1g95cd5cc6d',
attribute_name => 'AUTOPURGE',
attribute_value => 'NO');
DBMS_OUTPUT.put_line('Plans Altered: ' || l_plans_altered);
END;
/
把自动清除机制关闭了
往执行计划基库里添加新的计划。
1 添加索引
create index IX_OBJECT_NAME on TEST_OBJECTS (OBJECT_NAME
2使用提示强制走索引
select /*+ index(test_objects IX_OBJECT_NAME)*/ * from test_objects
执行成本特高
3从V$SQL_PLAN获得
select * (select *
4加入
SQL> DECLARE
2 k1 pls_integer;
3 begin
4 k1 := DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE
5 (
6 sql_handle=>'SQL_44c9b37ac97e85e9',
7 sql_id=>'5ptdb66rf053s',
8 plan_hash_value=>127622217
9 );
10 end;
11 /
PL/SQL procedure successfully completed
5查询基库
SQL_HANDLE SQL_TEXT PLAN_NAME ORIGIN PARSING_SCHEMA_NAME ENABLED ACCEPTED FIXED REPRODUCED AUTOPURGE OPTIMIZER_COST
SQL_44c9b37ac97e85e9 SQL_PLAN_49kdmgb4rx1g95cd5cc6d MANUAL-LOAD SCOTT YES YES NO YES NO 275
SQL_44c9b37ac97e85e9 <CLOB> SQL_PLAN_49kdmgb4rx1g9f1aba20d MANUAL-LOAD SCOTT YES YES NO YES YES 35397
我们成功加入了!!
6 启用我们走索引的计划
SET SERVEROUTPUT ON
DECLARE
l_plans_altered PLS_INTEGER;
BEGIN
l_plans_altered := DBMS_SPM.alter_sql_plan_baseline(
sql_handle => 'SQL_44c9b37ac97e85e9',
plan_name => 'SQL_PLAN_49kdmgb4rx1g9f1aba20d',
attribute_name => 'FIXED',
attribute_value => 'YES');
DBMS_OUTPUT.put_line('Plans Altered: ' || l_plans_altered);
END;
/
7验证
8有了基库后普通的V$SQL_PLAN 就没它了
FROM V$SQL_PLAN
WHERE SQL_ID = '0g9hjnj0x1nbb'
9 删除基库里一个执行计划
SET SERVEROUTPUT ON
DECLARE
l_plans_dropped PLS_INTEGER;
BEGIN
l_plans_dropped := DBMS_SPM.drop_sql_plan_baseline (
sql_handle => 'SQL_44c9b37ac97e85e9',
plan_name => 'SQL_PLAN_49kdmgb4rx1g95cd5cc6d');
DBMS_OUTPUT.put_line(l_plans_dropped);
END;
/
总结下,SPM是好东西,直接开启参数产生基库,然后使用人工进化非智能模式。基库唯一缺点没有SQLID,无法通过SQLID找到对应的基库,只能看SQLTEXT。