文章目录
- 0、参考资料
- 1、查看执行计划&指标解读
- 1.1、查看执行计划
- 1.1.1、explain plan命令
- 1.1.2、DBMS_XPLAN包
- 1.1.3、DISPLAY_SQL_PLAN_BASELINE函数
- 1.1.4、SQLPLUS中的AUTOTRACE
- 1.1.5、其它方式
- 1.1.6、如何得到最真实的执行计划
- 1.2、如何读懂执行计划
- 2、稳固执行计划
0、参考资料
书籍:
崔华大师的《基于SQL优化》
hello dba的《SQL based CBO》
Jonathan Lewis的《Cost Based Oracle Fundamentals》
梁敬彬、梁敬泓兄弟的《收获,不止SQL优化--抓住SQL的本质》
官方文档:
performing tuning guide -> Using EXPLAIN PLAN
Database PL/SQL Packages and Types Reference -> DBMS_XPLAN
1、查看执行计划&指标解读
1.1、查看执行计划
1.1.1、explain plan命令
explain plan for select * from t_users where user_id=:A;
select * from table(dbms_xplan.display);
说明:在Oracle 10g及其以上的版本里,对目标SQL执行explain plan命令,则Oracle就将解析目标SQL所产生的执行计划的具体步骤写入PLAN_TABLE中将这些具体执行步骤以格式化方式显示出来。
PLAN_TABLE$表是一个ON COMMIT PRESERVE ROWS的GLOBAL TEMPORARY TABLE,所以这里Oracle可以做到各个session只能看到自己执行的SQL所产生的执行计划,并且互不干扰。
1.1.2、DBMS_XPLAN包
–方法1
select * from table(dbms_xplan.display);
–方法2
select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
–方法3,常用,最好方式
select * from table(dbms_xplan.display_cursor('sql_id/hash_value',child_cursor_number/null,'advanced'));
–方法4,从awr中查看历史执行计划
select * from table(dbms_xplan.display_awr('sql_id',plan_hash_value/null));
–方法5,从sqlset中提取
select * from table(dbms_xplan.display_sqlset('STS_2','d76shb2rbmmsn',null,'BASIC ROWS COST'));
方法1需要和上面的explain plan命令配合使用;
方法2它用于在sqlplus中查看刚刚执行过的SQL执行计划。这里针对方法DBMS_XPLAN.DISPLAY_CURSOR所传入的第一个和第二个参数的值均为null,第三个参数的值是advanced,第三个输入参数值也可以是all,只不过用advanced后显示的结果会比用all的显示结果更详细一些。
方法3这里针对方法DBMS_XPLAN.DISPLAY_CURSOR所传入的第一个参数的值是指定SQL的SQL ID或者SQL hash value,第二个参数的值是要查看的执行计划所在的child cursor number。
方法4是从awr报告中查看历史执行计划信息。但是awr中把执行计划中的谓词条件省掉了,这点非常不好。要正常调用DISPLAY_AWR 参数,必须对以下视图有权限:DBA_HIST_SQL_PLAN 和DBA_HIST_SQLTEXT 的SELECT方法2、3只能从当前sga shared pool中查看到,如果被sga out,就看不到了
。
方法5 sqlset方式:DISPLAY_SQLSET 函数显示存储在一个SQL 调优集中的语句的执行计划。
declare
ss_name varchar2(30);
begin
ss_name := dbms_sqltune.create_sqlset();
dbms_sqltune.capture_cursor_cache_sqlset(ss_name,300,100);
dbms_output.put_line(ss_name);
end;
/
STS_2
PL/SQL procedure successfully completed.
select sqlset_name,sql_id,sql_text from DBA_SQLSET_STATEMENTS where upper(sql_text)
like 'SELECT * FROM T_USERS%';
select * from table(dbms_xplan.display_sqlset('STS_2','d76shb2rbmmsn',null,'BASIC ROWS COST'));
1.1.3、DISPLAY_SQL_PLAN_BASELINE函数
DISPLAY_SQL_PLAN_BASELINE 函数显示存储在数据字典当中SQL 执行计划基线的计划。
select * from table(dbms_xplan.display_sql_plan_baseline(sql_handle =>'SYS_SQL_99cc41808e350a83'));
1.1.4、SQLPLUS中的AUTOTRACE
说明:要使用AUTOTRACE,需要先做以下准备,用DBA 用户创建角色PLUSTRCE,并将该角色赋予用户;使用autotrace查看执行计划时,实际sql语句已经被执行了,这点需要小心,特别是获取一些dml语句执行计划的时候。
conn sys/sys as sysdba
Connected.
@?/SQLPLUS/ADMIN/PLUSTRCE.SQL
grant plustrace to demo;
SQL>SET TIMING ON --控制显示执行时间统计数据
SQL>SET AUTOTRACE ON EXPLAIN --这样设置包含执行计划、脚本数据输出,没有统计信息
SQL>set autotrace trace explain
SQL>执行需要查看执行计划的SQL语句
SQL>SET AUTOTRACE OFF --不生成AUTOTRACE报告,这是缺省模式
SQL> SET AUTOTRACE ON --这样设置包含执行计划、统计信息、以及脚本数据输出
SQL>执行需要查看执行计划的SQL语句
SQL>SET AUTOTRACE OFF
SQL> SET AUTOTRACE TRACEONLY --这样设置会有执行计划、统计信息,不会有脚本数据输出
SQL>执行需要查看执行计划的SQL语句
SQL>SET AUTOTRACE TRACEONLY STAT --这样设置只包含有统计信息
SQL>执行需要查看执行计划的SQL语句
1.1.5、其它方式
10046/10053事件、图形化工具、写脚本从VSQL_PLAN_MONITOR等视图中来获取执行计划等,因为不常用,不再一一列举
1.1.6、如何得到最真实的执行计划
DBMS_XPLAN包查看执行计划的章节中,方法2、方法3、方法4都是可以获取真实执行计划的方法,因为sql都实际执行过了。
执行计划中显示实际行数(A-Rows)和执行次数(Starts),调优常用
执行sql前提条件:
-
一般在会话级别设置参数STATISTICS_LEVEL为ALL,也可以使用/+ GATHER_PLAN_STATISTICS/提示。
-
若DBMS_XPLAN.DISPLAY_CURSOR中的入参SQL_ID输入值为NULL的话,则SERVEROUTPUT必须设置为OFF(SET SERVEROUTPUT OFF),否则会报错。
步骤如下:
SET SERVEROUTPUT OFF
ALTER SESSION SET STATISTICS_LEVEL=ALL;
设置完成后执行SQL语句
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ADVANCED ALLSTATS LAST'));
或
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(SQL_ID =>'',CURSOR_CHILD_NO =>1,FORMAT => 'ADVANCED ALLSTATS'));
DBMS_XPLAN.DISPLAY_CURSOR参数解释如下:
其中参数SQL_ID为父游标,如果为NULL,那么表示显示该会话之前的SQL执行计划。CURSOR_CHILD_NO为子游标的序号,默认为0,如果设定为NULL,那么所有该父游标下所有的子游标的执行计划都将返回。参数FORMAT指定要显示哪些信息,常用的有:IOSTATS(I/O信息显示)、ALLSTATS(I/O信息显示+PGA信息)、ADVANCED(显示所有统计信息)、IOSTATS LAST或ALLSTATS LAST(只显示最后一次执行的统计信息)。默认值TYPICAL只能显示一个普通的执行计划,不能显示出实际返回的行。
更加详细用法以及参数说明参考官方文档。
1.2、如何读懂执行计划
1.2.1、执行计划阅读顺序
从一个例子说明:
示例:
SQL> SET AUTOTRACE TRACEONLY; -- 只显示执行计划,不显示结果集
SQL> select * from scott.emp a,scott.emp b where a.empno=b.mgr;
已选择13行。
sqlplus中显示的执行计划
----------------------------------------------------------
Plan hash value: 992080948
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 13 | 988 | 6 (17)| 00:00:01 |
| 1 | MERGE JOIN | | 13 | 988 | 6 (17)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| EMP | 14 | 532 | 2 (0)| 00:00:01 |
| 3 | INDEX FULL SCAN | PK_EMP | 14 | | 1 (0)| 00:00:01 |
|* 4 | SORT JOIN | | 13 | 494 | 4 (25)| 00:00:01 |
|* 5 | TABLE ACCESS FULL | EMP | 13 | 494 | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("A"."EMPNO"="B"."MGR")
filter("A"."EMPNO"="B"."MGR")
5 - filter("B"."MGR" IS NOT NULL)
统计信息
----------------------------------------------------------
0 recursive calls
0 db block gets
11 consistent gets
0 physical reads
0 redo size
2091 bytes sent via SQL*Net to client
416 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
13 rows processed
上面的执行结果显示,在SQLPLUS里面看到的执行计划不是那么直接。
我们一般按缩进长度来判断,缩进最大的最先执行(最右边的),如果有2行缩进一样,那么就先执行上面的。
借用崔华的口诀:先从最开头一直连续往右看,直到看到最右边并列的地方;对于不并列的,靠右的先执行;如果遇到并列的,就从上往下看,对于并列的部分,靠上的先执行。
如果使用toad这样的图形工具,就可以清楚的显示执行的顺序:
1.2.2、执行计划指标解释
- 执行计划中字段解释:
ID: 一个序号,但不是执行的先后顺序。执行的先后根据缩进来判断。
Operation: 当前操作的内容。
Rows: 当前操作的Cardinality,Oracle估计当前操作的返回结果集。
Cost(CPU):Oracle 计算出来的一个数值(代价),用于说明SQL执行的代价。
Time:Oracle 估计当前操作的时间。
- 谓词说明:
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("A"."EMPNO"="B"."MGR")
filter("A"."EMPNO"="B"."MGR")
5 - filter("B"."MGR" IS NOT NULL)
Access: 表示这个谓词条件的值将会影响数据的访问路劲(表还是索引)。
Filter:表示谓词条件的值不会影响数据的访问路劲,只起过滤的作用。
在谓词中主要注意access,要考虑谓词的条件,使用的访问路径是否正确。
- 统计信息说明:
db block gets : 从buffer cache中读取的block的数量
consistent gets: 从buffer cache中读取的undo数据的block的数量
physical reads: 从磁盘读取的block的数量
redo size: DML生成的redo的大小
sorts (memory) :在内存执行的排序量
sorts (disk) :在磁盘上执行的排序量
Physical Reads通常是我们最关心的,如果这个值很高,说明要从磁盘请求大量的数据到Buffer Cache里,通常意味着系统里存在大量全表扫描的SQL语句,这会影响到数据库的性能,因此尽量避免语句做全表扫描,对于全表扫描的SQL语句,建议增 加相关的索引,优化SQL语句来解决。
关于physical reads ,db block gets 和consistent gets这三个参数之间有一个换算公式:
数据缓冲区的使用命中率=1 - ( physical reads / (db block gets + consistent gets) )。
用以下语句可以查看数据缓冲区的命中率:
SQL>SELECT name, value FROM v$sysstat WHERE name IN ('db block gets', 'consistent gets','physical reads');
查询出来的结果Buffer Cache的命中率应该在90%以上,否则需要增加数据缓冲区的大小。
它们三者之间的关系大致可概括为:
逻辑读指的是Oracle从内存读到的数据块数量。一般来说是’consistent gets’ + ‘db block gets’。当在内存中找不到所需的数据块的话就需要从磁盘中获取,于是就产生了’physical reads’
2、稳固执行计划
资料位置:E:\1@Repository IT\1.0@Oracle\1.0.2@性能优化\执行计划&SQL plan\sql执行计划稳固
How to Use SQL Plan Management (SPM) - Plan Stability Worked Example (Doc ID 456518.1)
参考:
2.0、把SQL执行计划从shared pool刷出去
keep 执行计划请参考:How to Pin a Cursor in the Shared Pool using DBMS_SHARED_POOL.KEEP (Doc ID 726780.1)
刷执行计划父游标如下:
select sql_text,sql_id,version_count,executions,OBJECT_STATUS,address,hash_value from v$sqlarea where sql_id='atv649gxpk8ga';
select PLAN_HASH_VALUE,q'[exec sys.dbms_shared_pool.purge(']'||address||','||hash_value||q'[','C');]' as flush_sql from v$sqlarea where sql_id='63u74y7gdafzf';
2.1、SQL Profile方式固定执行计划
2.1.1、SQL Profile管理
SELECT task_name,status FROM DBA_ADVISOR_TASKS;
select * from dba_profiles;
禁用profile
begin
dbms_sqltune.alter_sql_profile(
name => 'SYS_SQLPROF_01534b8309b90000',
attribute_name => 'status',
value => 'disabled');
end;
/
启用profile
begin
dbms_sqltune.alter_sql_profile(
name => 'SYS_SQLPROF_01534b8309b90000',
attribute_name => 'status',
value => 'enabled');
end;
/
删除profile或解绑profile
BEGIN
DBMS_SQLTUNE.DROP_SQL_PROFILE(name => 'SYS_SQLPROF_01534b8309b90000');
END;
/
2.1.2、Automatic SQL Profile
通过sqltuning方式来创建SQL Profile;automatic类型的SQL profile其实就是针对目标SQL的一些额外调整信息,并且存储在数据字典里。Oracle在产生执行计划时就根据它对目标SQL所涉及的统计信息等内容做调整,因此可以一定程度上避免错误的执行计划。automatic类型的SQL profile本质上针对目标SQL的额外信息调整,所以原始SQL的统计信息等内容发生改变后该SQL的执行计划也可能发生变化。所以automatic类型的SQL Profile并不能完全祈祷稳定目标SQL的执行计划,虽然它确实可以用来调整执行计划。
测试和创建过程如下:使用sql tuning advisor
1)创建表,插入数据,建立索引,收集统计信息
create table t1 (a number,b number,c varchar2(10));
declare
na integer;
nc varchar2(10) := 'test1';
a1 integer :=&minnum;
a2 integer :=&maxnum;
nb integer :=a2+1;
begin
for na in a1..a2
loop
insert into t1 (a, b, c) values (na,nb-na,nc);
end loop;
commit;
end;
/
create index t1_idx on t1(a);
begin dbms_stats.gather_table_stats(ownname=>'SYS',tabname=>'T1',method_opt=>'for all columns size 1',cascade => true);end;
/
2)执行SQL语句并且故意走错执行计划
select /*+ no_index(t1 t1_idx) */ * from t1 where a=100;
select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
3)执行SQL tuning任务
DECLARE
my_task_name VARCHAR2(30);
my_sqltext CLOB;
BEGIN
my_sqltext := 'select /*+ no_index(t1 t1_idx) */ * from t1 where a=100';
my_task_name := DBMS_SQLTUNE.CREATE_TUNING_TASK(sql_text => my_sqltext,
user_name => 'SYS',
scope => 'COMPREHENSIVE',
time_limit => 60,
task_name => 'tuning_sql_test',
description => 'Task to tune a query on a specified table');
DBMS_SQLTUNE.EXECUTE_TUNING_TASK(task_name => 'tuning_sql_test');
END;
/
4)查看sqltuning结果
SELECT DBMS_SQLTUNE.REPORT_TUNING_TASK( 'tuning_sql_test') from DUAL;
5)接受sql profile
execute dbms_sqltune.accept_sql_profile(task_name => 'tuning_sql_test',task_owner => 'SYS', replace => TRUE);
2.1.3、Manual SQL Profile
Manual类型的SQL Profile本质上就是一堆Hint的组合,这一堆Hint的组合实际上来源于执行计划中OutlineData部分的Hint组合。Manual类型的SQLProfile同样可以在不更改SQL的SQL文本的情况下调整其执行计划,而且相对于automatic类型的SQLprofile可以很好的稳定目标SQL的执行计划。
创建Manual类型的SQL Profile可以借助MOS上的文档SQLT(SQLTXPLAIN)-Tool that helps to diagnose SQL statements performing poorly [ID 215187.1]中的脚本coe_xfr_sql_profile.sql。这个脚本适合从shared pool、awr repository中指定SQL的执行计划的Outline Data部分的Hint组合,来创建Manul类型的SQL Profile。
2.1.3.1、场景1:shared pool中已经有比较好的执行计划
执行执行coe_xfr_sql_profile.sql脚本,选取代价比较小的执行计划即可。
2.1.3.2、场景2:没有好的执行计划可供选择,需要手工构造比较优的执行计划
1)针对目标SQL使用脚本coe_xfr_sql_profile.sql产生能生成其Manual类型的SQL Profile的脚本A。
2)改写目标SQL的文本,在其中使用合适的Hint,直到加入Hint后SQL能走出想要的执行计划。然后对加入合适Hint后的SQL使用脚本coe_xfr_sql_profile.sql,产生能生成其Manual类型的SQL Profile的脚本B。
3)使用脚本B中的OutlineData部分的Hint组合替换掉脚本A中的OutlineData部分的Hint组合。
4)执行修改后的脚本A生成针对原目标SQL的Manual类型SQL Profile.
方法1:手动的乐趣,使用B脚本中的类似如下部分内容去替换A脚本中部分:
h := SYS.SQLPROF_ATTR(
q'[BEGIN_OUTLINE_DATA]',
q'[IGNORE_OPTIM_EMBEDDED_HINTS]',
q'[OPTIMIZER_FEATURES_ENABLE('11.1.0.7')]',
q'[DB_VERSION('11.1.0.7')]',
q'[ALL_ROWS]',
q'[OUTLINE_LEAF(@"SEL$1")]',
q'[FULL(@"SEL$1" "T1"@"SEL$1")]',
q'[END_OUTLINE_DATA]');
方法2: 通过import_sql_profile手动增加import_sql_profile
begin
dbms_sqltune.import_sql_profile
(name=>'sp_01',
sql_text=>'select object_name from t1 where object_id=10',
replace=>true,
profile=>sqlprof_attr('FULL(t1@SEL$1)')) ;
end;
/
进化版本:通过dblink方式获取读库上sql文本
DECLARE
clsql_text varchar2(3000);
BEGIN
SELECT SQL_TEXT INTO clsql_text FROM v$sqlarea@R12201_TO_DG1 WHERE sql_id = '&SQL_ID';
DBMS_SQLTUNE.IMPORT_SQL_PROFILE( sql_text => clsql_text, profile => sqlprof_attr('USE_INVISIBLE_INDEXES'), name => 'PROFILE_&SQL_ID', force_match => TRUE );
END;
/
其中sqlprof_attr部分就是上面提到的替换部分的内容。sql profile中的hint信息都是存放在sys.sqlobj$data中的comp_data的栏位中,以xml格式存储的也可以通过从这个栏位来获取sqlprof_attr信息。
2.1.3.3、coe_xfr_sql_profile.sql改良版脚本
来自Oracle内部员工的“真香”脚本。
脚本:https://github.com/AlbertCQY/scripts/tree/master/oracle 目录下sql_profile_new2.sql脚本
脚本用法:
@sql_profile_new2.sql
Parameter 1:
SQL_ID (required)
Enter value for 1: --这里传入需要优化的sqlid
Parameter 2:
PLAN_HASH_VALUE (required)
Enter value for 2: --这里传入正确执行计划的PLAN_HASH_VALUE,可以不是Parameter 1对应sqlid的plan_hash
最后一步是执行生成的sql脚本。
2.2、SPM&SQL Baseline&基线
2.2.1、SQL Plan Baseline基本特点
1)通过OPTIMIZER_USE_SQL_PLAN_BASELINE来控制Oracle是否使用基线,默认值为TRUE,即会自动使用基线。
2)11g中默认是不会自动创建基线
3)与OUTLINE和SQL Profile不同,基线中不存在分类的概念
4)与OUTLINE和SQL Profile不同,每个SQL语句可以有多个基线。Oracle根据制定的规则来判断具体是否哪个基线
5)基线针对RAC中所有的实例都生效
6)基线有两个标示,一个为sql_handle,可以理解为表示语句文本的唯一标识,一个为sql_plan_name可以理解为执行计划的唯一标识
7)不能像sql profile一样通过force_matching属性将字面值不一样的SQL语句使用一个基线应用多个语句。
2.2.2、创建基线的几种方式
- 自动捕获基线,通过将optimizer_capture_sql_plan_baselines设置为true(默认值为false),优化器为重复执行两次以上的SQL语句生成并保存基线(可以系统级或会话级修改)
alter session set optimizer_capture_sql_plan_baselines = true;
或
alter system set optimizer_capture_sql_plan_baselines = true;
- 手动load
如下图所示,有4中途径:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RIJp3Nhx-1574082782220)(686D090B38BA40B3A4C07FE27E6165AA)]
1、从 SQL Tuning Set STS 导DBMS_SPM.LOAD_PLANS_FROM_SQLSET;
2、从Stored Outlines 中导入DBMS_SPM.MIGRATE_STORED_OUTLINE;
3、从内存中存在的计划中导入DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE;
4、通过staging table从另外一个系统中移植
DBMS_SPM.CREATE_STGTAB_BASELINE
DBMS_SPM.PACK_STGTAB_BASELINE
DBMS_SPM.UNPACK_STGTAB_BASELINE
(注意:这些导入的baseline都会被自动标记为ACCEPTED)
最常用的方法:
- 从库缓存中加载,通过包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调优集合中加载,通过使用包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;
/
2.2.3、基线的几种状态
一个SQL语句对应的基线,我将它们归纳为三种状态
accepted(可接受),只有这种状态的基线,优化器才会考虑此基线中的执行计划
no-accepted(不可接受),这种状态的基线,优化器在SQL语句解析期间不会考虑。这种状态的基线必须通过演化和验证通过后,转变为accepted状态后,才会被优化器考虑使用
fixed为yes(固定),这种状态的基线固有最高优先级!比其他两类基线都要优先考虑
2.2.4、查看基线
-
基本视图:dba_sql_plan_baselines、dba_sql_management_config
-
底层视图:sqlobj (保存具体的hint),如下查看基线中保存的执行计划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;
- 通过函数来查看基线的详细信息:
select * from table(dbms_xplan.display_sql_plan_baseline(sql_handle=>'SYS_SQL_11bcd50cd51504e9',plan_name=>'SQL_PLAN_13g6p1maja1790cce5f0e'));
2.2.5、演化基线
为了验证基线中一个处于不可接受状态的执行计划是否比一个处于可接受状态的执行计划具有更高的效率,必须通过演化来验证,需要让优化器以不同的执行计划来执行这条SQL语句,观察不可接受状态的执行计划基线是否会带来更好的性能,如果性能确实更高,这个不可接受状态的基线将会转换为可接受状态。演化的方式有两种:
1)手工执行运行
SELECT DBMS_SPM.evolve_sql_plan_baseline(sql_handle => 'SYS_SQL_xxxxxxxxxxxxx') From dual;
还有time_limit/verify/commit几个参数,可以参考文档
- 调优包实现基线的自动演化,可以理解为,启动一个调度任务,周期性的检查是否有不可接受状态的基线可以被演化
2.2.6、修改基线
可以通过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 Altered: ' || v_text );
END;
/
2.2.7、删除基线
- 可以通过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;
/
2.2.8、迁移基线
dbms_spm提供了多个过程来在数据库之间迁移SQL计划基线
- create_stgtab_baseline创建一个计划基线保存表
- pack_stgtab_baseline将基线从数据字典复制到第一步的表中
- unpack_stgtab_baseline将基线从保存表中复制到迁移数据库的数据字典中
大概过程如下: - 创建一张保存数据字典中基线表内容的用户表
exec dbms_spm.create_stgtab_baseline(table_name => 'BASELINE_TEST',table_owner => 'SCOTT',tablespace_name =>'');
- 将数据字典中基线表的内容 插入到 第一步创建的用户表中
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.3、SPM方式固定执行计划
2.3.1、已经有好的执行计划
手工load比较优的执行计划即可:
DECLARE
l_plans_loaded PLS_INTEGER;
BEGIN
l_plans_loaded := DBMS_SPM.load_plans_from_cursor_cache(sql_id => '1fkh93md0802n',plan_hash_value=>3708376029);
END;
/
2.3.2、需要手工构造好的执行计划
这种情况一般是当前sql执行计划差,但是又不能修改sql文本。还是使用前面讲到的偷梁换柱思想,SPM方式处理起来相对来说比较简单。
假如说我已经把需要调优sql的执行计划load进去了,记录下sql_handle。但是执行计划不是我想要的。这时候我可以用hint构造一个理想的执行计划。然后加sql_handle参数把好的执行计划的sql_id和plan_hash_value加载进去。sql_handle还是原来的。
这时候可以看到同一个sql_handle下有两条执行计划,把原来的删除即可。
操作步骤如下:
- 加载目标SQL的执行计划
DECLARE
k1 pls_integer;
begin
k1 := DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE (
sql_id=>'4vaj9fgjysy9c', --目标SQL_ID
plan_hash_value=>1845196118); --目标SQL_planhash
end;
/
- 查询出刚才加载的目标SQL的sql_handle
select * from dba_sql_plan_baselines where ...
- hint 提示方式构造一个比较优的执行计划
这里记录下构造后sql的sql_id和sql plan hash - 进行替换
DECLARE
k2 pls_integer;
begin
k2 := DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE (
sql_id=>'fm35jcmypb3qu', --构造的sql_id
plan_hash_value=>2780970545, --构造sql的plan hash
sql_handle=>'SYS_SQL_11bcd50cd51504e9' ); --目标sql加载后的sql_handle
end;
/
- 删除第一步中加载的,不需要的执行计划
DECLARE
k1 pls_integer;
begin
k1 := DBMS_SPM.drop_sql_plan_baseline (
sql_handle=>'SYS_SQL_11bcd50cd51504e9',
plan_name=>'SQL_PLAN_13g6p1maja17934f41c8d'); --这里是同一个sql_handle下的错误执行计划的plan_name,即第一步生成的
end;
/
测试记录:
目的:让执行计划走上全表扫描
查询语句:select count(*) from wxh_tbd where object_id=:a
SQL_ID:85f05qy1aq0dr
PLAN_HASH_VALUE:1501268522
步骤一-------------------------创建测试表,根据DBA_OBJECTS创建,OBJECT_ID上有索引
Create table wxh_tbd as select * from dba_objects;
create index t_3 on wxh_tbd(object_id);
步骤二------------------------创建指定SQLID的BASELINE,后面要做修改,由于默认走的索引
declare
l_pls number;
begin
l_pls := DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE(sql_id => '85f05qy1aq0dr',
plan_hash_value => 1501268522,
enabled => 'NO');
end;
/
步骤三--------------------------想办法构造出执行计划为全表扫描的SQL_ID
var a number
exec :a :=1234
select /*+ full(wxh_tbd) */count(*) from wxh_tbd where object_id=:a;
select sql_id ,sql_text from v$sql where sql_text like '%wxh_tbd%';
查出SQL_ID为89143jku5hzcw,PLAN_HASH_VALUE为853361775
步骤四--------------------------确定原始执行计划的 sql_handle
select sql_handle, plan_name, origin, enabled, accepted,fixed,optimizer_cost,sql_text
from dba_sql_plan_baselines where sql_text like '%count(*) from wxh_tbd %'
order by last_modified;
SQL_HANDLE:SYS_SQL_ad9f0ff741832bd9
PLAN_NAME:SYS_SQL_PLAN_41832bd9d63f8aa9
步骤五------------------------与正确的执行计划做关联
declare l_pls number;
begin
l_pls := DBMS_SPM.load_plans_from_cursor_cache(sql_id => '89143jku5hzcw', -- hinted_SQL_ID'
plan_hash_value => 853361775, --hinted_plan_hash_value
sql_handle => 'SYS_SQL_ad9f0ff741832bd9' --sql_handle_for_original
);
end;
/
步骤六--------------------------删除错误的执行计划
declare l_pls number;
begin
l_pls := DBMS_SPM.DROP_SQL_PLAN_BASELINE(sql_handle => 'SYS_SQL_ad9f0ff741832bd9', --sql_handle_for_original
plan_name => 'SYS_SQL_PLAN_41832bd9d63f8aa9 ' --sql_plan_name_for_original
);
end;
/
步骤七----------------------确认是否使用到BASELINE
explain plan for select count(*) from wxh_tbd where object_id=:a;
select * from table(dbms_xplan.display);
--------------------------------------
| Id | Operation | Name |
--------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | SORT AGGREGATE | |
|* 2 | TABLE ACCESS FULL| WXH_TBD |
--------------------------------------
Note
-----
- SQL plan baseline "SYS_SQL_PLAN_41832bd9cca3d082" used for this statement