学习之路——Oracle执行计划

—摘要
  作为从事了Oracle dba运维这个岗位一年多的从业者,很多时候会产生一种只需要会搭搭环境、做做备份、导导数据、处理一些简单故障就足够生存下去的错觉,显然,这样是不够的,每次遇到自己处理不了的问题就得找二线、找专家的时候,挫败感油然而生。
  那身为一个dba应该提升自己的什么能力呢?那就应该问问一个数据库最重要的是什么呢?那当然是数据!那与数据关系最密切的又是什么呢?那就是SQL,一条好的SQL对于一个程序、一个应用是多么的重要,所以就有了SQL优化。
  优化这门学问,水真的很深,最近我打开了一年前买的而至今没怎么看的崔华专家写的《基于Oracle的SQL优化》,从基础学起,做自己觉得喜欢而有意义的事,晚一点、慢一点都没关系。

Oracle执行计划概况图:
在这里插入图片描述

一.什么是执行计划

  一条简单的select-SQL包含什么呢?查询所有字段还是其中某几列?单表查询还是多表联合查询?是否包含where条件?是否有group by分组?还是order by排序?等等,这意味着执行一条SQL语句需要在Oracle内部实现很多步骤,那Oracle用来执行目标SQL语句的这些步骤的组合就被称为执行计划。
  一份执行计划可以分为三部分:

  1. 目标SQL正文、SQL_ID、执行计划对应的PLAN HASH VALUE
  2. 执行计划的主体部分
  3. 执行计划的额外补充信息
二. 查看执行计划方法

  Oracle数据库里的SQL优化绝大多数都可以归结为针对目标SQL执行计划的调整,那如何获取一条SQL的执行计划呢?方法有很多,这里就只介绍最常用的4种方法:

1. explain plan 命令
explain plan for + 目标SQL_TEXT;
select * from table(dbms_xplan.display);

  explain plan将解析目标SQL所产生的执行计划写入PLAN_TABLE$ (各个session可看到自己执行SQL的执行计划的全局临时表),随后执行的select * from table(dbms_xplan.display)只是从PLAN_TABLE$ 中将具体的执行步骤以格式化的方式显示。
我们可以看下示例:

SQL> explain plan for select * from test_emp;
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 242355602
------------------------------------------------------------------------------
| Id  | Operation           | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |          |    14 |  1218 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS FULL  | TEST_EMP |    14 |  1218 |     3   (0)| 00:00:01 |
------------------------------------------------------------------------------
Note
-----
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
   - dynamic sampling used for this statement (level=2)
12 rows selected.
SQL> select count(*) from sys.plan_table$;

  COUNT(*)
----------
         2
SQL> set linesize 200
SQL> set pagesize 1000
SQL> col OPTIONS for a30
SQL> select operation,options,object_name,id,cardinality,cost from sys.plan_table$;
OPERATION          OPTIONS     OBJECT_NAME      ID     CARDINALITY    COST
-----------------  ----------  ---------------  -----  -------------  -------------
SELECT STATEMENT                                0      14             3
TABLE ACCESS       FULL        TEST_EMP         1      14             3

##与上面查到的执行计划一一对应
2. DBMS_XPLAN包

  使用DBMS_XPLAN包获取目标SQL的执行计划主要有4种方式,针对不同的应用场景,选择使用其中的一种方法去得到执行计划:
  第一种方法不用多说,很熟悉了,与explain plan相结合来使用;

select * from table(dbms_xplan.display); 

  第二种方法用于在sqlplus中查看刚刚执行过的sql的执行计划,第三个值也可以用”all”,不过”advanced”的显示结果比”all”更加详细;

select * from table(dbms_xplan.display_cursor(null,null,'advanced')); 

  第三种方法用于查看指定sql的执行计划

select * from table(dbms_xplan.display_cursor('sql_id/hash_value',child_cursor_number,'advanced'));   

  第四种方法用于查看指定SQL的所有历史执行计划

select * from table(dbms_xplan.display_awr('sql_id')); 

  方法2、3都是需要目标SQL的执行计划还在Shared Pool中才能够查询得到显示,但是目标SQL的执行计划已经被清出Shared Pool,那么只要该SQL被采集到了AWR Repository中,就可以用方法4来查看该SQL的历史执行计划。

3. AUTOTRACE开关

  在SQLPLUS中设置AUTOTRACE开关的语法如下所示:

SET AUTOTRACE {OFF | ON | TRACEONLY}
[EXPLAIN]
[STATISTICS]

  以下是各种语法搭配的介绍:

  • 显示执行结果、执行计划、统计信息
SET AUTOTRACE ON
  • 关闭Autotrace,只显示执行结果,此为默认值
SET AUTOTRACE OFF
  • 不显示执行结果,显示结果数量、执行计划、统计信息
SET AUTOTRACE TRACEONLY
  • 只显示执行计划
SET AUTOTRACE TRACEONLY EXPLAIN
  • 只显示结果数量、统计信息
SET AUTOTRACE TRACEONLY STATISTICS
4. 10046事件

  使用10046事件得到目标SQL的执行计划与其他方法不同的地方在于它明确显示了目标SQL实际执行计划中的每一个执行步骤所消耗的逻辑读、物理读和花费的时间,这种细粒度的明细显示对于诊断复杂SQL的性能问题尤为有用。
  使用10046事件得到执行计划的具体执行步骤如下:
  1) 在当前会话Session激活10046事件

alter session set events '10046 trace name context forever,level 12';
oradebug event 10046 trace name context forever,level 12

  2) 在当前会话Session执行目标SQL
  3) 在当前会话Session关闭10046事件

alter session set events '10046 trace name context off';
oradebug event 10046 trace name context off

  4) tkprof 格式化10046事件所产生的trace文件

tkprof 原_file_name 新_file_name

以下做个示例:
1) 开启当前Session使用oradebug命令:

SQL> oradebug setmypid

2) 在当前会话Session激活10046事件

SQL> oradebug event 10046 trace name context forever,level 12

3) 在当前会话Session执行目标SQL

SQL> select count(*) from test.test_emp;
  COUNT(*)
----------
        14

4) 查询当前Session激活10046事件后多对应的trace文件

SQL> oradebug tracefile_name
/u01/oracle/diag/rdbms/lzw/lzw/trace/lzw_ora_37104.trc

5) 在当前会话Session关闭10046事件

SQL> oradebug event 10046 trace name context off

6) 使用tkprof工具格式化trace文件

$ tkprof /u01/oracle/diag/rdbms/lzw/lzw/trace/lzw_ora_37104.trc /home/oracle/test_emp_plan.trc

7) 查看格式化后的trace文件中目标SQL的执行计划

$ more /home/oracle/test_emp_plan.trc

SQL ID: fchrjm3zt2wwd Plan Hash: 2170825879
select count(*) from test.test_emp

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          1          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        2      0.00       0.00          0          3          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.00       0.00          0          4          0           1

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: SYS
Number of plan statistics captured: 1

Rows (1st) Rows (avg) Rows (max)  Row Source Operation
---------- ---------- ----------  ---------------------------------------------------
         1          1          1  SORT AGGREGATE (cr=3 pr=0 pw=0 time=16 us)
        14         14         14   TABLE ACCESS FULL TEST_EMP (cr=3 pr=0 pw=0 time=10 us cost=3 size=0 card=14)
三. 真实的执行计划

  在Oracle中怎么判断目标SQL的执行计划是真实的呢?其实很简单,那就是看目标SQL是否真正的执行过,真正执行过的SQL所对应的执行计划就是真实的,所以在上面介绍的众多查询SQL的执行计划方法中有以下方式得到的执行计划为真实的:

110046事件
2select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
3select * from table(dbms_xplan.display_cursor('sql_id/hash_value',child_cursor_number,'advanced'));
4select * from table(dbms_xplan.display_awr('sql_id'));

  ##使用SET AUTOTRACE命令得到的执行计划都有可能是不真实的,因为使用SET AUTOTRACE命令所显示的执行计划都是来源于调用explain plan命令。

四. 执行计划的执行顺序

  不管是专家写的书籍,还是博主写的博客,都编写了属于自己的一份看懂执行计划的执行顺序的口令,这些口令是非常之好的,但是未必最适合自己,我们可以通过不断借鉴前辈们的经验,来编写自己的学习领悟,不管是这个过程还是结果,都会让自己受益匪浅,找到最适合自己的学习方式,所以以下也是我自己总结出的看懂执行计划顺序的三句话:

从上往下看,第一个没有子节点的最先执行;
先执行完所有子节点,才能执行父节点;  
遇到并列的部分,靠上的先执行。

  拿下面这段执行计划做个简单的示例:在这里插入图片描述
  我们从上往下看,会发现第一个没有子节点的就是ID=4的“ TABLE ACCESS FULL T2 ”,然后根据需要执行完所有的子节点才能执行父节点,那么接下来就是ID=5的“INDEX RANGE SCAN IX_CODE”、ID=3的“NESTED LOOPS”、ID=6的“TABLE ACCESS BY INDEX ROWID T1”,结合三句话,后面的顺序也就是ID=2的“NESTED LOOPS”、ID=8的”TABLE ACCESS FULL T1”、ID=7的“BUFFER SORT”、ID=1的“MERGE JOIN CARTESIAN”、ID=0的“SELECT STATEMENT”,所以这一段执行计划的执行顺序就是:4==>5==>3==>6==>2==>8==>7==>1==>0

五. 常见的执行计划

  这里我们就直接用示例来看看这些常见的执行计划吧:

1. 与表访问相关的执行计划
  • TABLE ACCESS FULL—全表扫描
SQL> select empno,ename,rowid from scott.emp where ename='SCOTT';
EMPNO     ENAME      ROWID
--------    ----------  ------------------
7788        SCOTT      AAAVREAAEAAAACXAAH
SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced'));

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------
SQL_ID  fqvvq5519bn1k, child number 0
-------------------------------------
select empno,ename,rowid from scott.emp where ename='SCOTT'

Plan hash value: 3956160932
--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |       |       |     3 (100)|          |
|*  1 |  TABLE ACCESS FULL| EMP  |     1 |    32 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------
  • TABLE ACCESS BY USER ROWID—ROWID扫描(rowid源于用户指定)
SQL> select empno,ename from scott.emp where rowid='AAAVREAAEAAAACXAAH';

     EMPNO ENAME
---------- ----------
      7788 SCOTT
SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced'));

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------
SQL_ID  cjfhzuxa59gw7, child number 0
-------------------------------------
select empno,ename from scott.emp where rowid='AAAVREAAEAAAACXAAH'

Plan hash value: 1116584662
-----------------------------------------------------------------------------------
| Id  | Operation                  | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT           |      |       |       |     1 (100)|          |
|   1 |  TABLE ACCESS BY USER ROWID| EMP  |     1 |    32 |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------
  • TABLE ACCESS BY INDEX ROWID—ROWID扫描(rowid源于索引)
SQL> select empno,ename from scott.emp where empno=7788;

     EMPNO ENAME
---------- ----------
      7788 SCOTT
SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced'));

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
SQL_ID  6y35cw2wawujg, child number 0
-------------------------------------
select empno,ename from scott.emp where empno=7788

Plan hash value: 2949544139
--------------------------------------------------------------------------------------
| Id  | Operation                   | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |        |       |       |     2 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| EMP    |     1 |    20 |     2   (0)| 00:00:01 |
|*  2 |   INDEX UNIQUE SCAN         | PK_EMP |     1 |       |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------------------

##因为emp表的empno有索引PK_EMP,所以这里的ROWID来源于索引。

2. 与B树索引相关的执行计划

  先创建一个测试表:

SQL> create table test1(id number,name varchar2(1));

  插入7条数据并提交

SQL> insert into test1 values(99,'A');
SQL> insert into test1 values(100,'A');
SQL> insert into test1 values(101,'B');
SQL> insert into test1 values(102,'B');
SQL> insert into test1 values(103,'B');
SQL> insert into test1 values(104,'B');
SQL> insert into test1 values(105,'B');
SQL> commit;
  • INDEX UNIQUE SCAN—索引唯一扫描

  在ID列上创建唯一性索引

SQL> create unique index idx_uni_test1 on test1(id);
SQL> select * from test1 where id=100;

SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
SQL_ID  304p57f3awv0n, child number 0
-------------------------------------
select * from test1 where id=100

Plan hash value: 2920889977
---------------------------------------------------------------------------------------------
| Id  | Operation                   | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |               |       |       |     1 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| TEST1         |     1 |    15 |     1   (0)| 00:00:01 |
|*  2 |   INDEX UNIQUE SCAN         | IDX_UNI_TEST1 |     1 |       |     0   (0)|          |
---------------------------------------------------------------------------------------------
  • INDEX RANGE SCAN—索引范围扫描

  删除测试表上的唯一性索引

SQL> drop index idx_uni_test1;

  重新创建一个非唯一性索引

SQL> create index idx_test1_1 on test1(id);
SQL> select /*+ index(test1 idx_test1_1) */* from test1 where id=100;
SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
SQL_ID  d2vs2qkd79a8q, child number 0
-------------------------------------
select /*+ index(test1 idx_test1_1) */* from test1 where id=100

Plan hash value: 4186374959
-------------------------------------------------------------------------------------------
| Id  | Operation                   | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |             |       |       |     2 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| TEST1       |     1 |    15 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | IDX_TEST1_1 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------
  • INDEX FAST FULL SCAN—索引快速全扫描

  truncate test1表的数据

SQL> truncate table test1;

  重新插入10000行数据

SQL> begin
     for i in 1..5000
     loop
     insert into test1 values(i,'A');
     end loop;
     commit;
     end;
     /
SQL> begin 
     for i in 5001..10000
     loop
     insert into test1 values(i,'B');
     end loop;
     commit;
     end;
     /

  对test1表收集统计信息

SQL> exec dbms_stats.gather_table_stats(ownname =>'SYS',tabname => 'TEST1',estimate_percent => 100,cascade => TRUE,no_invalidate => false,method_opt => 'FOR ALL COLUMNS SIZE 1');

  将ID这一列设置为非空字段

SQL> alter table test1 modify(id not null);
SQL> select id from test1;

SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  ftkg04k3d00n7, child number 0
-------------------------------------
select id from test1

Plan hash value: 836885815
--------------------------------------------------------------------------------
| Id  | Operation            | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             |       |       |     7 (100)|          |
|   1 |  INDEX FAST FULL SCAN| IDX_TEST1_1 | 10000 | 40000 |     7   (0)| 00:00:01 |
------------------------------------------------------------------------------------
  • INDEX FULL SCAN—索引全扫描
SQL> select /*+ index(test1 idx_test1_1)*/id from test1;

select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  8avnt05yszym9, child number 0
-------------------------------------
select /*+ index(test1  idx_test1_1)*/id from test1

Plan hash value: 1011581059
--------------------------------------------------------------------------------
| Id  | Operation        | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |             |       |       |    20 (100)|          |
|   1 |  INDEX FULL SCAN | IDX_TEST1_1 | 10000 | 40000 |    20   (0)| 00:00:01 |
--------------------------------------------------------------------------------
  • INDEX SKIP SCAN—索引跳跃式扫描

  删除索引idx_test1_1,创建复合索引idx_test1_2,前导列为name,第二列为id

SQL> drop index idx_test1_1;
SQL> create index idx_test1_2 on test1(name,id);
SQL> select * from test1 where id=100;

SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  304p57f3awv0n, child number 0
-------------------------------------
select * from test1 where id=100

Plan hash value: 858043409
--------------------------------------------------------------------------------
| Id  | Operation        | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |             |       |       |     3 (100)|          |
|*  1 |  INDEX SKIP SCAN | IDX_TEST1_2 |     1 |     6 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------------
3. 其他典型的执行计划

  此部分就不做详细的说明了,下面简单的介绍下其中比较常见的几种执行计划:

  • AND-EQUAL (INDEX MERGE)
    索引合并,当where条件中出现多个针对不同单列的等值条件,并这些列都有单键值的索引,则Oracle可能会以相应的单个等值条件去分别扫描这些索引,然后合并这些扫描单个索引所得的rowid集合,如果能从集合中值相同的rowid,Oracle只需用这些rowid回表即可得到最终执行结果。

  • INDEX JOIN
    Index join针对单表上的不同索引之间的连接

  • VIEW
    Oracle在处理包含视图的sql时,根据该视图是否可以做视图合并(View Merging),其对应的执行计划有两种方式:
    1.如果可以做视图合并,则Oracle在执行该SQL时可以直接针对该视图的基表,此时SQL的执行计划很可能不会出现“VIEW”;
    2.如果不能做视图合并,则Oracle将把该视图看作一个整体并独立地执行,此时SQL的执行计划将会出现关键字“VIEW”

  • FILTER
    过滤、筛选,这是一种特殊的执行计划,对应的过程有三步:1.得到一个驱动结果集;2.根据一定的过滤条件从上述结果集中滤除不满足条件的记录;2.剩下的记录就会返回给最终用户或下一个执行步骤。

  • SORT
    排序,执行计划中通常以组合的方式出现,如:SORT AGGREGATE、SORT UNIQUE、SORT JOIN、SORT GROUP BY、SORT ORDER BY、BUFFER SORT。
    需要注意的是执行计划中即使出现关键字“SORT”,也不一定是需要排序。

  • UNION/UNION ALL
    表示对两个结果集进行合并,UNION ALL仅仅是简单地将两个结果集合并,并不做任何额外的处理;UNION除了合并之外,还会对合并后的结果进行排序去重。

六. 执行计划的稳定

  在生产环境中执行的SQL,我们都希望CBO能够产生正确的执行计划,但是有时候会因为各种各样的原因导致CBO会产生效率不高,甚至错误的执行计划,而且在日常运维中,我们会常常遇到一条SQL本来跑得好好的,突然慢得影响业务,这种SQL执行效率突然的衰减往往与目标SQL执行计划的改变有关。这种情况下,我们主要有两种方法可以用来调整和稳定目标SQL的执行计划:

1. SQL Profile

  SQL Profile主要是Oracle10g的工具,它主要能实现在不能修改目标SQL的SQL文本的情况下使目标SQL按指定的执行计划运行,锁住、稳定其执行计划。SQL Profile主要有两种类型:

  • Automatic SQL Profile

  创建一个测试表,插入1万行,并创建索引

SQL> create table test2(id number);
SQL> declare
     begin
     for i in 1..10000
     loop
     insert into test2 values(i);
     end loop;
     commit;
     end;
     /

SQL> create index idx_test2 on test2(id);

  收集统计信息

SQL> exec dbms_stats.gather_table_stats(ownname => 'sys',tabname => 'test2',method_opt => 'for all columns size 1',cascade => true);

  执行目标SQL,并查看执行计划

SQL> select /*+ no_index(test2 idx_test2) */ * from test2 where id=1;

SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  2h6rp4fp4h8nt, child number 0
-------------------------------------
select /*+ no_index(test2 idx_test2) */ * from test2 where id=1

Plan hash value: 300966803
---------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |       |       |       |     7 (100)|          |
|*  1 |  TABLE ACCESS FULL| TEST2 |     1 |     4 |     7   (0)| 00:00:01 |
---------------------------------------------------------------------------

  很显然这条加了Hint的SQL走了全表扫(TABLE ACCESS FULL),但是正确的应该是走对IDX_TEST2的索引范围扫描(INDEX RANGE SCAN)。

  创建一个自动调整任务使用SQL Tuning Advisor来尝试对SQL生成Automatic SQL Profile

SQL> declare
        my_task_name varchar2(30);
        my_sqltext CLOB;
     begin
        my_sqltext := 'select /*+ no_index(test2 idx_test2) */ * from test2 where id=1';
        my_task_name := dbms_sqltune.create_tuning_task(
                sql_text => my_sqltext,
                user_name => 'SYS',
                scope => 'COMPREHENSIVE',
                time_limit => 60,
                task_name => 'my_sql_tuning_task',
                description => 'Task to tune a query on table test2');
     end;
     /

  执行自动调整任务

SQL> begin
     dbms_sqltune.execute_tuning_task(task_name => 'my_sql_tuning_task');
     end;
     /

SQL> set long 9000
SQL> set longchunksize 1000
SQL> set linesize 800
SQL> select dbms_sqltune.report_tuning_task( 'my_sql_tuning_task') from dual;
DBMS_SQLTUNE.REPORT_TUNING_TASK('MY_SQL_TUNING_TASK')
-------------------------------------------------------------------------------
GENERAL INFORMATION SECTION
-------------------------------------------------------------------------------
Tuning Task Name   : my_sql_tuning_task
Tuning Task Owner  : SYS
Workload Type      : Single SQL Statement
Scope              : COMPREHENSIVE
Time Limit(seconds): 60
Completion Status  : COMPLETED
Started at         : 11/18/2019 19:17:50
Completed at       : 11/18/2019 19:17:50

DBMS_SQLTUNE.REPORT_TUNING_TASK('MY_SQL_TUNING_TASK')
-----------------------------------------------------------------
-------------------------------------------------------------------------------
Schema Name: SYS
SQL ID     : atatj8g3j1gxc
SQL Text   : select /*+ no_index(test2 idx_test2) */ * from test2 where id=1
-------------------------------------------------------------------------------
FINDINGS SECTION (1 finding)
-------------------------------------------------------------------------------
1- SQL Profile Finding (see explain plans section below)
--------------------------------------------------------
DBMS_SQLTUNE.REPORT_TUNING_TASK('MY_SQL_TUNING_TASK')
-------------------------------------------------------------------------------------
  A potentially better execution plan was found for this statement.

  Recommendation (estimated benefit: 90.91%)
  ------------------------------------------
  - Consider accepting the recommended SQL profile.
    execute dbms_sqltune.accept_sql_profile(task_name =>
            'my_sql_tuning_task', task_owner => 'SYS', replace => TRUE);
  Validation results
  ------------------
  The SQL profile was tested by executing both its plan and the original plan
DBMS_SQLTUNE.REPORT_TUNING_TASK('MY_SQL_TUNING_TASK')
-------------------------------------------------------------------------------------
  and measuring their respective execution statistics. A plan may have been
  only partially executed if the other could be run to completion in less time.

                           Original Plan  With SQL Profile  % Improved
                           -------------  ----------------  ----------
  Completion Status:            COMPLETE          COMPLETE
  Elapsed Time (s):             .000084           .000007      91.66 %
  CPU Time (s):                   .0001                 0        100 %
  User I/O Time (s):                  0                 0
  Buffer Gets:                       22                 2       90.9 %
  Physical Read Requests:             0                 0…
…
EXPLAIN PLANS SECTION
-------------------------------------------------------------------------------
1- Original With Adjusted Cost
------------------------------
Plan hash value: 300966803
--------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
DBMS_SQLTUNE.REPORT_TUNING_TASK('MY_SQL_TUNING_TASK')
-----------------------------------------------------------------
----------
|   0 | SELECT STATEMENT  |       |     1 |     4 |     7   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| TEST2 |     1 |     4 |     7   (0)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("ID"=1)
2- Using SQL Profile

DBMS_SQLTUNE.REPORT_TUNING_TASK('MY_SQL_TUNING_TASK')
---------------------------------------------------------------------------------------
Plan hash value: 1855540135
------------------------------------------------------------------------------
| Id  | Operation        | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |           |     1 |     4 |     1   (0)| 00:00:01 |
|*  1 |  INDEX RANGE SCAN| IDX_TEST2 |     1 |     4 |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------

  从上述调整结果可以看出Oracle已经找到了更好的执行计划,并且已经创建了针对该SQL的Automatic SQL Profile,如果接受这个调整,目标SQL的响应时间将有91.66 %的改善,逻辑读将有90.9 %的改善,并且会由之前的全表扫变成索引范围扫描。

  最后只需根据Oracle的提示接收这个SQL Profile

execute dbms_sqltune.accept_sql_profile(task_name =>'my_sql_tuning_task', task_owner => 'SYS', replace => TRUE);
SQL> select /*+ no_index(test2 idx_test2) */ * from test2 where id=1;
SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------
SQL_ID  2h6rp4fp4h8nt, child number 0
-------------------------------------
select /*+ no_index(test2 idx_test2) */ * from test2 where id=1

Plan hash value: 1855540135
------------------------------------------------------------------------------
| Id  | Operation        | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |           |       |       |     1 (100)|          |
|*  1 |  INDEX RANGE SCAN| IDX_TEST2 |     1 |     4 |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------

  从上述执行计划可以看出,在不改变SQL的情况下,SQL Profile确实改变了SQL的执行计划,但是这时如果SQL文本发生一点改动,那么原有的SQL Profile就会失效,这里就要配合FORCE_MATCH参数的使用:

execute dbms_sqltune.accept_sql_profile(task_name =>'my_sql_tuning_task', task_owner => 'SYS', replace => TRUE,force_match => true);
SQL> select /*+ no_index(test2 idx_test2) */ * from test2 where id=2;

SQL>  select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------
SQL_ID  agznsf177yju6, child number 0
-------------------------------------
select /*+ no_index(test2 idx_test2) */ * from test2 where id=2

Plan hash value: 1855540135

------------------------------------------------------------------------------
| Id  | Operation        | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |           |       |       |     1 (100)|          |
|*  1 |  INDEX RANGE SCAN| IDX_TEST2 |     1 |     4 |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------

  使用了FORCE_MATCH参数,即使where条件输入的值改变了,SQL Profile也可以起到作用。

  • Manual SQL Profile

  Manual SQL Profile本质上就是一堆Hint组合,来源于执行计划中Outline Data部分的Hint组合这里需要使用到coe_xfr_sql_profile.sql,这个脚本可以在metalink上下载,如果没有账号的朋友可以留言,我会把sql发给你,以下是使用Manual SQL Profile的具体步骤:
  还是刚才创建的测试表,先把生成的Automatic SQL Profile删除:

SQL> exec dbms_sqltune.drop_sql_profile('SYS_SQLPROF_016e829f93290001');

  原本SQL的执行计划

SQL> select /*+ no_index(test2 idx_test2) */ * from test2 where id=3;

SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------
SQL_ID  8p7zdsna4jk2y, child number 0
-------------------------------------
select /*+ no_index(test2 idx_test2) */ * from test2 where id=3

Plan hash value: 300966803
---------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |       |       |       |     7 (100)|          |
|*  1 |  TABLE ACCESS FULL| TEST2 |     1 |     4 |     7   (0)| 00:00:01 |
---------------------------------------------------------------------------

  改写Hint的SQL的执行计划

SQL> select /*+ index(test2 idx_test2) */ * from test2 where id=3;
SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------
SQL_ID  drv0zwkyrb5wr, child number 0
-------------------------------------
select /*+ index(test2 idx_test2) */ * from test2 where id=3

Plan hash value: 1855540135
----------------------------------------------------------------------------
| Id  | Operation        | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT |           |       |       |     1 (100)|          |
|*  1 |  INDEX RANGE SCAN| IDX_TEST2 |     1 |     4 |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------

  查看两条sql的信息

SQL> select sql_text,sql_id,version_count from v$sqlarea where sql_text like '%id=3%';
SQL_TEXT                                                                            SQL_ID          VERSION_COUNT
---------------------------------------------------------------------------------   --------------  -------------
select sql_text,sql_id,version_count from v$sqlarea where sql_text like '%id=3%'    9c5fm1hxusqar   1

select /*+ no_index(test2 idx_test2) */ * from test2 where id=3                     8p7zdsna4jk2y   1

select /*+ index(test2 idx_test2) */ * from test2 where id=3                        drv0zwkyrb5wr   1

  查看SQL对应的PLAN HASH VALUE

原目标SQLSQL> select plan_hash_value from v$sql where sql_id='8p7zdsna4jk2y';
PLAN_HASH_VALUE
---------------
      300966803

加了Hint后的SQL:
SQL> select plan_hash_value from v$sql where sql_id='drv0zwkyrb5wr';
PLAN_HASH_VALUE
---------------
     1855540135

  上传coe_xfr_sql_profile.sql,针对SQL执行coe_xfr_sql_profile.sql

SQLSQL> @/home/oracle/coe_xfr_sql_profile.sql
Values passed to coe_xfr_sql_profile:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SQL_ID         : "8p7zdsna4jk2y"
PLAN_HASH_VALUE: "300966803"

Execute coe_xfr_sql_profile_8p7zdsna4jk2y_300966803.sql

改写后的SQLSQL> @/home/oracle/coe_xfr_sql_profile.sql
Values passed to coe_xfr_sql_profile:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SQL_ID         : "drv0zwkyrb5wr"
PLAN_HASH_VALUE: "1855540135"

Execute coe_xfr_sql_profile_drv0zwkyrb5wr_1855540135.sql

  将改写后SQL的SQL_Profile文件中的以下这段替换原SQL的SQL_Profile文件的以下段:

改动后SQL:
h := SYS.SQLPROF_ATTR(
q'[BEGIN_OUTLINE_DATA]',
q'[IGNORE_OPTIM_EMBEDDED_HINTS]',
q'[OPTIMIZER_FEATURES_ENABLE('11.2.0.4')]',
q'[DB_VERSION('11.2.0.4')]',
q'[ALL_ROWS]',
q'[OUTLINE_LEAF(@"SEL$1")]',
q'[INDEX(@"SEL$1" "TEST2"@"SEL$1" ("TEST2"."ID"))]',
q'[END_OUTLINE_DATA]');SQL:
h := SYS.SQLPROF_ATTR(
q'[BEGIN_OUTLINE_DATA]',
q'[IGNORE_OPTIM_EMBEDDED_HINTS]',
q'[OPTIMIZER_FEATURES_ENABLE('11.2.0.4')]',
q'[DB_VERSION('11.2.0.4')]',
q'[ALL_ROWS]',
q'[OUTLINE_LEAF(@"SEL$1")]',
q'[FULL(@"SEL$1" "TEST2"@"SEL$1")]',
q'[END_OUTLINE_DATA]');

  执行已修改后的coe_xfr_sql_profile_8p7zdsna4jk2y_300966803.sql

SQL> @/home/oracle/coe_xfr_sql_profile_8p7zdsna4jk2y_300966803.sql

  查看原SQL现在的执行计划

SQL> select /*+ no_index(test2 idx_test2) */ * from test2 where id=3;

SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------
SQL_ID  8p7zdsna4jk2y, child number 0
-------------------------------------
select /*+ no_index(test2 idx_test2) */ * from test2 where id=3

Plan hash value: 1855540135
----------------------------------------------------------------------------
| Id  | Operation        | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT |           |       |       |     1 (100)|          |
|*  1 |  INDEX RANGE SCAN| IDX_TEST2 |     1 |     4 |     1   (0)| 00:00:01 |
----------------------------------------------------------------------------

##与Automatic SQL Profile相同,如果需要改变SQL的where输入的值,也能使用Manual SQL Profile,需要在刚才的coe_xfr_sql_profile_8p7zdsna4jk2y_300966803.sql中将“force_match”参数改为TRUE。

2. SPM(SQL Plan Management)

  Oracle 11g推出了新工具SPM(SQL Plan Management),SPM是一种主动的稳定执行计划的手段,能够保证只有被验证过的执行计划才会被启用。上面说的SQL Profile方法来稳定目标SQL的执行计划,那就意味着很可能失去了继续优化SQL的执行效率的机会,SPM可以说是彻底解决了执行计划稳定性的问题,它既能够主动地稳定执行计划,又保留了继续使用新的执行效率可能更高的执行计划的机会。
  启用SPM后,每一个SQL都会存在对应的SQL Plan Baseline,这里面存储的就是该SQL的执行计划,我们可以在视图DBA_SQL_PLAN_BASELINES中查看目标SQL的所有SQL Plan Baseline,其中的ENABLED和ACCEPTED这两列用来描述一个SQL Plan Baseline多对应的执行计划是否能被Oracle启用,均为YES则表示已启用。
  Oracle11g中,有两种方法可以产生目标SQL的SQL Plan Baseline:

  • 自动捕获

  参数optimizer_capture_sql_plan_baselines用于控制是否开启自动捕获SQL Plan Baseline,默认值为FALSE,可在Session和系统级别动态修改;optimizer_use_sql_plan_baselines用于是否启用SQL Plan Baseline,默认值TRUE,可在Session和系统级别动态修改。
  以下做个测试:
  在当前Session禁用SPM并同时开启自动捕获SQL Plan Baseline

SQL> alter session set optimizer_use_sql_plan_baselines=FALSE;
SQL> alter session set optimizer_capture_sql_plan_baselines=TRUE;

  创建测试表并添加索引

SQL> create table test3 as select * from dba_objects;
SQL> create index idx_test3 on test3(object_id);

  收集此表的统计信息

SQL> exec dbms_stats.gather_table_stats(ownname => 'SYS',tabname => 'TEST3',estimate_percent => 100,cascade => true);
SQL> select object_id,object_name from test3 where object_id between 100 and 105;
SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------
SQL_ID  6mmfucvzy369v, child number 0
-------------------------------------
select object_id,object_name from test3 where object_id between 100 and
105

Plan hash value: 3735083495
------------------------------------------------------------------------------------------
| Id  | Operation                   | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |           |       |       |     3 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| TEST3     |     7 |   210 |     3   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | IDX_TEST3 |     7 |       |     2   (0)| 00:00:01 |
------------------------------------------------------------------------------------------

  查看该SQL是否有对应的SQL Plan Baseline

SQL> select sql_handle,plan_name,origin,enabled,accepted,sql_text from dba_sql_plan_baselines where sql_text like 'select object_id%';
no rows selected

  SQL只执行一次,Oracle不会自动捕获其SQL Plan Baseline。

  再次执行该SQL,查看是否有SQL Plan Baseline

SQL> select object_id,object_name from test3 where object_id between 100 and 105;

SQL> select sql_handle,plan_name,origin,enabled,accepted,sql_text from dba_sql_plan_baselines where sql_text like 'select object_id%';
SQL_HANDLE                     PLAN_NAME                         ORIGIN         ENA             ACC              SQL_TEXT
---------------------          ------------------------------    -------------  ---------       --------         ------------------------------------------------------------------------------------
SQL_69de3b2923b336b6           SQL_PLAN_6mrjv54jv6dpqd1683003    AUTO-CAPTURE   YES             YES              select object_id,object_name from test3 where object_id between 100 and 105

  SQL已重复执行,所以现在Oracle就会自动捕获其SQL Plan Baseline,其中ENABLED和ACCEPTED的值均为“YES”。

  现将IDX_TEST3的聚簇因子修改为2400万,目的是为了能让SQL的执行计划变为对表TEST3的全表扫描

SQL> exec dbms_stats.set_index_stats(ownname => 'SYS',indname => 'IDX_TEST3',clstfct => 24000000,no_invalidate => false);

SQL> select index_name,clustering_factor from dba_indexes where index_name='IDX_TEST3';
INDEX_NAME                     CLUSTERING_FACTOR
------------------------------ -----------------
IDX_TEST3                               24000000
SQL> select object_id,object_name from test3 where object_id between 100 and 105;

SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  6mmfucvzy369v, child number 0
-------------------------------------
select object_id,object_name from test3 where object_id between 100 and
105

Plan hash value: 3306317399
---------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |       |       |       |   339 (100)|          |
|*  1 |  TABLE ACCESS FULL| TEST3 |     7 |   210 |   339   (1)| 00:00:05 |
---------------------------------------------------------------------------

  再次查看dba_sql_plan_baselines视图

SQL> select sql_handle,plan_name,origin,enabled,accepted,sql_text from dba_sql_plan_baselines where sql_text like 'select object_id%';
SQL_HANDLE                     PLAN_NAME                         ORIGIN         ENA             ACC                SQL_TEXT
---------------------          -------------------------------   -------------  -------         ------------       -----------------------------------------------------------------------------
SQL_69de3b2923b336b6           SQL_PLAN_6mrjv54jv6dpq7e6c5d22    AUTO-CAPTURE   YES             NO                 select object_id,object_name from test3 where object_id between 100 and 105

SQL_69de3b2923b336b6           SQL_PLAN_6mrjv54jv6dpqd1683003    AUTO-CAPTURE   YES             YES                select object_id,object_name from test3 where object_id between 100 and 105

  在当前Session禁用SPM并同时开启自动捕获SQL Plan Baseline

SQL> alter session set optimizer_use_sql_plan_baselines=TRUE;
SQL> alter session set optimizer_capture_sql_plan_baselines= FALSE;

  现在的IDX_TEST3的聚簇因子还是2400万

SQL> select index_name,clustering_factor from dba_indexes where index_name='IDX_TEST3';
INDEX_NAME                     CLUSTERING_FACTOR
------------------------------ -----------------
IDX_TEST3                               24000000

  再次执行SQL,查看执行计划

SQL> select object_id,object_name from test3 where object_id between 100 and 105;
SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  6mmfucvzy369v, child number 2
-------------------------------------
select object_id,object_name from test3 where object_id between 100 and
105

Plan hash value: 3735083495
-----------------------------------------------------------------------------------------
| Id  | Operation                   | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |           |       |       |  1908 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| TEST3     |     7 |   210 |  1908   (0)| 00:00:23 |
|*  2 |   INDEX RANGE SCAN          | IDX_TEST3 |     7 |       |     2   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------
Note
-----
   - SQL plan baseline SQL_PLAN_6mrjv54jv6dpqd1683003 used for this statement

  从上述可以看出,目标SQL的执行计划从全表扫变回了对索引IDX_TEST3的索引范围扫描,Note部分SQL plan baseline SQL_PLAN_6mrjv54jv6dpqd1683003,表明了SPM开启的情况下,Oracle依然会选择ENABLED和ACCEPTED的值均为“YES”的SQL plan baseline。

  如果在启用了SPM的情况下,想起用该SQL新的执行计划,如以下操作:

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

SQL_HANDLE                     PLAN_NAME                         ORIGIN         ENA             ACC            SQL_TEXT
------------------------       -----------------------------     ------------   -------         ---------      ------------------------------------------------------------------------------------
SQL_69de3b2923b336b6           SQL_PLAN_6mrjv54jv6dpq7e6c5d22    AUTO-CAPTURE   YES             NO             select object_id,object_name from test3 where object_id between 100 and 105

SQL_69de3b2923b336b6           SQL_PLAN_6mrjv54jv6dpqd1683003    AUTO-CAPTURE   YES             YES            select object_id,object_name from test3 where object_id between 100 and 105

  在11gR1中,只需要将SQL_PLAN_6mrjv54jv6dpqd1683003的ACCEPTED设置为NO就可以了:

SQL> var temp varchar2(1000);
SQL> exec:temp:=dbms_spm.alter_sql_plan_baseline(sql_handle => 'SQL_69de3b2923b336b6',plan_name => 'SQL_PLAN_6mrjv54jv6dpqd1683003',attribute_name => 'accepted',attribute_value => 'NO');

  但是在11gR2中,这样操作就会报错,需要使用DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE和DBMS_SPM.ALTER_SQL_PLAN_BASELINE达到启用新执行计划的目的:

  先将新执行计划的ACCEPTED的值设为YES

SQL> exec:temp:=dbms_spm.evolve_sql_plan_baseline(sql_handle => 'SQL_69de3b2923b336b6',plan_name => 'SQL_PLAN_6mrjv54jv6dpq7e6c5d22',verify => 'NO',commit => 'YES');
SQL> select sql_handle,plan_name,origin,enabled,accepted,sql_text from dba_sql_plan_baselines where sql_text like 'select object_id%';

SQL_HANDLE                     PLAN_NAME                         ORIGIN         ENA             ACC           SQL_TEXT
------------------------       ------------------------------    -------------  -------        ---------      -------------------------------------------------------------------------------------
SQL_69de3b2923b336b6           SQL_PLAN_6mrjv54jv6dpq7e6c5d22    AUTO-CAPTURE   YES             YES           select object_id,object_name from test3 where object_id between 100 and 105

SQL_69de3b2923b336b6           SQL_PLAN_6mrjv54jv6dpqd1683003    AUTO-CAPTURE   YES             YES           select object_id,object_name from test3 where object_id between 100 and 105

  然后再将原先执行计划的ENABLED的值设为‘NO’

SQL> exec:temp:=dbms_spm.alter_sql_plan_baseline(sql_handle => 'SQL_69de3b2923b336b6',plan_name => 'SQL_PLAN_6mrjv54jv6dpqd1683003',attribute_name => 'ENABLED',attribute_value => 'NO');
SQL> select sql_handle,plan_name,origin,enabled,accepted,sql_text from dba_sql_plan_baselines where sql_text like 'select object_id%';

SQL_HANDLE                     PLAN_NAME                         ORIGIN         ENA             ACC          SQL_TEXT
------------------------       ------------------------------    -------------  ------          -----        --------------------------------------------------------------------------------------
SQL_69de3b2923b336b6           SQL_PLAN_6mrjv54jv6dpq7e6c5d22    AUTO-CAPTURE   YES             YES          select object_id,object_name from test3 where object_id between 100 and 105

SQL_69de3b2923b336b6           SQL_PLAN_6mrjv54jv6dpqd1683003    AUTO-CAPTURE   NO              YES          select object_id,object_name from test3 where object_id between 100 and 105

  再次执行SQL,查看执行计划

SQL> select object_id,object_name from test3 where object_id between 100 and 105;

SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  6mmfucvzy369v, child number 1
-------------------------------------
select object_id,object_name from test3 where object_id between 100 and
105

Plan hash value: 3306317399
---------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |       |       |       |   339 (100)|          |
|*  1 |  TABLE ACCESS FULL| TEST3 |     7 |   210 |   339   (1)| 00:00:05 |
---------------------------------------------------------------------------

  从上述测试结果来看,我们可以轻易地在目标SQL的多个执行计划中切换,所以SPM既能主动地稳定执行计划,又保留了继续使用新的执行计划的机会。

  • 手工生成

  手工生成SQL Plan Baseline的方式和Manual SQL Profile一样,可以在不改变目标SQL的情况下调整执行计划,以下做个示例:
  使用刚才的测试表做一个查询,获取执行计划

SQL> select /*+ no_index(test3 idx_test3) */object_name,object_id from test3 where object_id=7;
SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  335s7c506w0bz, child number 0
-------------------------------------
select /*+ no_index(test3 idx_test3) */object_name,object_id from test3
where object_id=7

Plan hash value: 3306317399
---------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |       |       |       |   339 (100)|          |
|*  1 |  TABLE ACCESS FULL| TEST3 |     1 |    30 |   339   (1)| 00:00:05 |
---------------------------------------------------------------------------

  没有开启自动捕获,目标SQL还未生成对应的SQL Plan Baseline

SQL> select sql_handle,plan_name,origin,enabled,accepted,sql_text from dba_sql_plan_baselines where sql_text like 'select /*+ no_index(test3 idx_test3) */object_name%';
no rows selected

  使用目标SQL的初始执行计划对应的信息手工生成SQL Plan Baseline

SQL> var temp number
SQL> exec:temp:=dbms_spm.load_plans_from_cursor_cache(sql_id => '335s7c506w0bz',plan_hash_value => 3306317399);
SQL> select sql_handle,plan_name,origin,enabled,accepted,sql_text from dba_sql_plan_baselines where sql_text like 'select /*+ no_index(test3 idx_test3) */object_name%';

SQL_HANDLE                     PLAN_NAME                         ORIGIN         ENA             ACC         SQL_TEXT
-------------------------      -------------------------------   -------------  -----           ------      ------------------------------------------------------------------------------------
SQL_e2a8001297a557c9           SQL_PLAN_f5a002abuapy97e6c5d22    MANUAL-LOAD    YES             YES         select /*+ no_index(test3 idx_test3) */object_name,object_id from test3 where ob

  改写原目标SQL,加入Hint重新执行

SQL> select /*+ index(test3 idx_test3) */object_name,object_id from test3 where object_id=7;
SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  43yfvtarchtu8, child number 0
-------------------------------------
select /*+ index(test3 idx_test3) */object_name,object_id from test3
where object_id=7

Plan hash value: 3735083495
-----------------------------------------------------------------------------------------
| Id  | Operation                   | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |           |       |       |   277 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| TEST3     |     1 |    30 |   277   (0)| 00:00:04 |
|*  2 |   INDEX RANGE SCAN          | IDX_TEST3 |     1 |       |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------

  用改写后的SQL的执行计划多对应的SQL_ID和PLAN HASH VALUE以及原SQL的SQL Plan Baseline的sql_handle来生成新的SQL Plan Baseline

SQL> exec:temp:=dbms_spm.load_plans_from_cursor_cache(sql_id => '43yfvtarchtu8',plan_hash_value => 3735083495,sql_handle => 'SQL_e2a8001297a557c9');
SQL> select sql_handle,plan_name,origin,enabled,accepted,sql_text from dba_sql_plan_baselines where sql_text like 'select /*+ no_index(test3 idx_test3) */object_name%';

SQL_HANDLE                     PLAN_NAME                         ORIGIN         ENA             ACC            SQL_TEXT
-------------------------      ----------     ---------------    -------------  ----            ------         --------------------------------------------------------------------------------
SQL_e2a8001297a557c9           SQL_PLAN_f5a002abuapy97e6c5d22    MANUAL-LOAD    YES             YES            select /*+ no_index(test3 idx_test3) */object_name,object_id from test3 where ob

SQL_e2a8001297a557c9           SQL_PLAN_f5a002abuapy9d1683003    MANUAL-LOAD    YES             YES            select /*+ no_index(test3 idx_test3) */object_name,object_id from test3 where ob

  删除原SQL的执行计划所对应的SQL Plan Baseline

SQL> exec:temp:=dbms_spm.drop_sql_plan_baseline(sql_handle => 'SQL_e2a8001297a557c9',plan_name => 'SQL_PLAN_f5a002abuapy97e6c5d22');
SQL> select sql_handle,plan_name,origin,enabled,accepted,sql_text from dba_sql_plan_baselines where sql_text like 'select /*+ no_index(test3 idx_test3) */object_name%';

SQL_HANDLE                     PLAN_NAME                         ORIGIN         ENA             ACC           SQL_TEXT
-------------------------      -----------------------------     ------------   -------         -------       ---------------------------------------------------------------------------------
SQL_e2a8001297a557c9           SQL_PLAN_f5a002abuapy9d1683003    MANUAL-LOAD    YES             YES           select /*+ no_index(test3 idx_test3) */object_name,object_id from test3 where ob

  再次执行原目标SQL,查看其执行计划

SQL> select /*+ no_index(test3 idx_test3) */object_name,object_id from test3 where object_id=7;
SQL> select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID  335s7c506w0bz, child number 2
-------------------------------------
select /*+ no_index(test3 idx_test3) */object_name,object_id from test3
where object_id=7

Plan hash value: 3735083495
-----------------------------------------------------------------------------------------
| Id  | Operation                   | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |           |       |       |   277 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| TEST3     |     1 |    30 |   277   (0)| 00:00:04 |
|*  2 |   INDEX RANGE SCAN          | IDX_TEST3 |     1 |       |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------
Note
-----
   - SQL plan baseline SQL_PLAN_f5a002abuapy9d1683003 used for this statement

  这样就实现了用SQL Plan Baseline在不改变SQL文本的情况下,改变执行计划。

  执行计划是Oracle数据库里SQL优化的基础,绝大多数Oracle数据库中的SQL优化都可以说是对SQL执行计划的调整,所以我们都应该学会看懂执行计划。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值