Oracle SQL Tuning_稳固执行计划03_SPM_自动捕获SQL Plan Baseline

为什么需要稳定执行计划

由于各种原因(如统计信息不准确,CBO成本计算公式缺陷)导致CBO产生效率不高、甚至错误的执行计划。
某个SQL原先跑的好好的,为什么突然慢的让人无法接受,这种效率衰减往往是因为目标SQL执行计划的改变。

CBO产生了错误的执行计划,我们要如何纠正

1. 重新收集统计信息,但有时不能解决问题
2. 修改目标SQL(比如在目标SQL中加入Hint)文本,第三方软件,不能修改源码
3. 使用SQL Profile(10g以上)或SPM(SQL Plan Management)(11g以上)

什么是SPM

SQL Profile实际上只是一种亡羊补牢、被动的技术手段,应用在那些执行计划已经发生了不好的变更的SQL上。
Oracle在11g中推出了SPM(SQL Plan Management)。SPM是一种主动的稳定执行计划的手段,能够保证只有被验证过的执行计划才会被启用,当由于种种原因(比如统计信息的变更)而导致目标SQL产生了新的执行计划后,这个新的执行计划并不会被马上启用,直到它已经被我们验证过其执行效率会比原先执行计划高才会被启用。
对于一些SQL,如果我们使用了SQL Profile来稳定目标SQL的执行计划,那就意味着很可能失去了继续优化上述SQL的执行效率的机会。而SPM的推出可以说彻底解决了执行计划稳定性的问题,它既能够主动地稳定执行计划,又保留了继续使用新的执行效率可能更高的执行计划的机会。

启用SPM后,每一个SQL都会存在对应的SQL Plan Baseline,SQL Plan Baseline里存储的就是该SQL的执行计划,可以从 DBA_SQL_PLAN_BASELINES 中查看 SQL Plan Baseline 。
DBA_SQL_PLAN_BASELINES 中的列 ENABLED 和 ACCEPTED 的值用来描述一个 SQL Plan Baseline所对应的执行计划是否被Oracle启用,只有 ENABLED 和 ACCEPTED 的值均为“YES”的SQL Plan Baseline所对应的执行计划才会被Oracle启用。

有两种方法可以产生目标SQL的SQL Plan Baseline。

1. 自动捕获
2. 手工生成/批量导入(批量导入尤其适用于数据库大版本的升级,可以确保升级后原有系统所有SQL的执行计划不会发生改变)

自动捕获 SQL Plan Baseline

参数 optimizer_capture_sql_plan_baselines 用于控制是否开启自动捕获SQL Plan Baseline,默认FALSE,在默认情况下,不会自动捕获SQL Plan Baseline。这个参数可以在Session和系统级别动态修改的。
参数 optimizer_use_sql_plan_baselines 用于控制是否启用 SQL Plan Baseline ,默认TRUE,在默认情况下,Oracle在生成执行计划时就会启用SPM,使用已有的SQL Plan Baseline。这个参数可以在Session和系统级别动态修改的。

SQL> show parameter sql_plan

PARAMETER_NAME                           TYPE        VALUE
---------------------------------------- ----------- --------------------
optimizer_capture_sql_plan_baselines     boolean     FALSE
optimizer_use_sql_plan_baselines         boolean     TRUE

在当前session中禁掉SPM并同时开启自动捕获 SQL Plan Baseline

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

创建测试环境 

create table t3 as select * from dba_objects;
create index idx_t3 on t3(object_id);
exec dbms_stats.gather_table_stats(ownname=>'ZYLONG',tabname=>'T3',method_opt=>'for all columns size 1',CASCADE=>true);

 执行下面查询语句,执行计划走INDEX RANGE SCAN

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

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

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------
SQL_ID  57khyp15hzbfx, child number 0
-------------------------------------
select object_id,object_name from t3 where object_id between 103 and 108

Plan hash value: 636101163

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

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

   1 - SEL$1 / T3@SEL$1
   2 - SEL$1 / T3@SEL$1

Outline Data
-------------

  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('11.2.0.4')
      DB_VERSION('11.2.0.4')
      ALL_ROWS
      OUTLINE_LEAF(@"SEL$1")
      INDEX_RS_ASC(@"SEL$1" "T3"@"SEL$1" ("T3"."OBJECT_ID"))
      END_OUTLINE_DATA
  */

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("OBJECT_ID">=103 AND "OBJECT_ID"<=108)

Column Projection Information (identified by operation id):
-----------------------------------------------------------

   1 - "OBJECT_NAME"[VARCHAR2,128], "OBJECT_ID"[NUMBER,22]
   2 - "T3".ROWID[ROWID,10], "OBJECT_ID"[NUMBER,22]

由于查询只执行了一次,所以dba_sql_plan_baselines里面没有数据

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

 再次执行查询

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

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

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------
SQL_ID  57khyp15hzbfx, child number 0
-------------------------------------
select object_id,object_name from t3 where object_id between 103 and 108

Plan hash value: 636101163

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

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

   1 - SEL$1 / T3@SEL$1
   2 - SEL$1 / T3@SEL$1

Outline Data
-------------

  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('11.2.0.4')
      DB_VERSION('11.2.0.4')
      ALL_ROWS
      OUTLINE_LEAF(@"SEL$1")
      INDEX_RS_ASC(@"SEL$1" "T3"@"SEL$1" ("T3"."OBJECT_ID"))
      END_OUTLINE_DATA
  */

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("OBJECT_ID">=103 AND "OBJECT_ID"<=108)

Column Projection Information (identified by operation id):
-----------------------------------------------------------

   1 - "OBJECT_NAME"[VARCHAR2,128], "OBJECT_ID"[NUMBER,22]
   2 - "T3".ROWID[ROWID,10], "OBJECT_ID"[NUMBER,22]
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_b82ee0eb24faf56b           SQL_PLAN_bhbr0xckgpxbb7a0cc8b4 AUTO-CAPTURE   YES YES select object_id,object_name from t3 where object_id between 103 and 108

将索引IDX_T3的聚簇因子修改为2400万。

exec dbms_stats.set_index_stats(ownname=>'ZYLONG',indname => 'IDX_T3', clstfct => 24000000, no_invalidate => false);

select index_name, clustering_factor from dba_indexes where index_name = 'IDX_T3';

INDEX_NAME                     CLUSTERING_FACTOR
------------------------------ -----------------
IDX_T3                                  24000000

重新执行目标SQL,执行计划变为 TABLE ACCESS FULL

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

select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------
SQL_ID  57khyp15hzbfx, child number 0
-------------------------------------
select object_id,object_name from t3 where object_id between 103 and 108

Plan hash value: 4161002650

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |       |       |   282 (100)|          |
|*  1 |  TABLE ACCESS FULL| T3   |     7 |   210 |   282   (1)| 00:00:04 |
--------------------------------------------------------------------------

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

   1 - SEL$1 / T3@SEL$1

Outline Data
-------------

  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('11.2.0.4')
      DB_VERSION('11.2.0.4')
      ALL_ROWS
      OUTLINE_LEAF(@"SEL$1")
      FULL(@"SEL$1" "T3"@"SEL$1")
      END_OUTLINE_DATA
  */

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(("OBJECT_ID"<=108 AND "OBJECT_ID">=103))

Column Projection Information (identified by operation id):
-----------------------------------------------------------

   1 - "OBJECT_NAME"[VARCHAR2,128], "OBJECT_ID"[NUMBER,22]

现在该SQL的执行计划已经从 INDEX RANGE SCAN 变为 TABLE ACCESS FULL,即执行计划发生了改变。

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_b82ee0eb24faf56b           SQL_PLAN_bhbr0xckgpxbb2dae97d6 AUTO-CAPTURE   YES NO  select object_id,object_name from t3 where object_id between 103 and 108
SQL_b82ee0eb24faf56b           SQL_PLAN_bhbr0xckgpxbb7a0cc8b4 AUTO-CAPTURE   YES YES select object_id,object_name from t3 where object_id between 103 and 108

对当前session关闭自动捕获 SQL Plan Baseline 并同时开启SPM,恢复默认

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

现在索引IDX_T3的聚簇因子依然是2400万。

SQL> select index_name, clustering_factor from dba_indexes where index_name = 'IDX_T3';

INDEX_NAME                     CLUSTERING_FACTOR
------------------------------ -----------------
IDX_T3                                  24000000

再次执行目标SQL,执行计划恢复 INDEX RANGE SCAN

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

select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------
SQL_ID  57khyp15hzbfx, child number 2
-------------------------------------
select object_id,object_name from t3 where object_id between 103 and 108

Plan hash value: 636101163

--------------------------------------------------------------------------------------
| Id  | Operation                   | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |        |       |       |  1906 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| T3     |     7 |   210 |  1906   (0)| 00:00:23 |
|*  2 |   INDEX RANGE SCAN          | IDX_T3 |     7 |       |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------------------

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

   1 - SEL$1 / T3@SEL$1
   2 - SEL$1 / T3@SEL$1

Outline Data
-------------

  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('11.2.0.4')
      DB_VERSION('11.2.0.4')
      ALL_ROWS
      OUTLINE_LEAF(@"SEL$1")
      INDEX_RS_ASC(@"SEL$1" "T3"@"SEL$1" ("T3"."OBJECT_ID"))
      END_OUTLINE_DATA
  */

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("OBJECT_ID">=103 AND "OBJECT_ID"<=108)

Column Projection Information (identified by operation id):
-----------------------------------------------------------

   1 - "OBJECT_NAME"[VARCHAR2,128], "OBJECT_ID"[NUMBER,22]
   2 - "T3".ROWID[ROWID,10], "OBJECT_ID"[NUMBER,22]

Note
-----
   - SQL plan baseline SQL_PLAN_bhbr0xckgpxbb7a0cc8b4 used for this statement

从以上内容可以看出,现在目标SQL的执行计划已经从 TABLE ACCESS FULL 恢复为 INDEX RANGE SCAN。上述执行计划Note部分有如下内容:“SQL plan baseline SQL_PLAN_bhbr0xckgpxbb7a0cc8b4 used for this statement”。
这表明在SPM已开启的情况下,即使目标SQL产生了新的执行计划,Oracle依然只会应用该SQL的 ENABLED 和 ACCEPTED 的值均为“YES”的SQL Plan Baseline所对应的执行计划。

如果想启用目标SQL新的执行计划(TABLE ACCESS FULL),应该如何做呢?

Oracle 11gR1 ,将正在使用的 SQL Plan Baseline 的 ACCEPTED 值改为 NO

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

Oracle 11gR2 ,不能修改 ACCEPTED 值,可以联合使用 DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE 和 DBMS_SPM.ALTER_SQL_PLAN_BASELINE

先使用 DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE 将新的执行计划(TABLE ACCESS FULL)的 SQL Plan Baseline 的 ACCEPTED 值改为 YES :

exec :temp := dbms_spm.evolve_sql_plan_baseline(sql_handle => 'SQL_b82ee0eb24faf56b', plan_name => 'SQL_PLAN_bhbr0xckgpxbb2dae97d6', 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_b82ee0eb24faf56b           SQL_PLAN_bhbr0xckgpxbb2dae97d6 AUTO-CAPTURE   YES YES select object_id,object_name from t3 where object_id between 103 and 108
SQL_b82ee0eb24faf56b           SQL_PLAN_bhbr0xckgpxbb7a0cc8b4 AUTO-CAPTURE   YES YES select object_id,object_name from t3 where object_id between 103 and 108

然后再使用 DBMS_SPM.ALTER_SQL_PLAN_BASELINE 将原先的执行计划(INDEX RANGE SCAN)所对应的 SQL Plan Baseline 的 ENABLED 值改为 NO :

exec :temp := dbms_spm.alter_sql_plan_baseline(sql_handle => 'SQL_b82ee0eb24faf56b', plan_name => 'SQL_PLAN_bhbr0xckgpxbb7a0cc8b4', 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_b82ee0eb24faf56b           SQL_PLAN_bhbr0xckgpxbb2dae97d6 AUTO-CAPTURE   YES YES select object_id,object_name from t3 where object_id between 103 and 108
SQL_b82ee0eb24faf56b           SQL_PLAN_bhbr0xckgpxbb7a0cc8b4 AUTO-CAPTURE   NO  YES select object_id,object_name from t3 where object_id between 103 and 108

再次执行目标SQL,已经启用新的执行计划 TABLE ACCESS FULL

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

select * from table(dbms_xplan.display_cursor(null,null,'advanced'));
PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------
SQL_ID  57khyp15hzbfx, child number 1
-------------------------------------
select object_id,object_name from t3 where object_id between 103 and 108

Plan hash value: 4161002650

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |       |       |   282 (100)|          |
|*  1 |  TABLE ACCESS FULL| T3   |     7 |   210 |   282   (1)| 00:00:04 |
--------------------------------------------------------------------------

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

   1 - SEL$1 / T3@SEL$1

Outline Data
-------------

  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('11.2.0.4')
      DB_VERSION('11.2.0.4')
      ALL_ROWS
      OUTLINE_LEAF(@"SEL$1")
      FULL(@"SEL$1" "T3"@"SEL$1")
      END_OUTLINE_DATA
  */

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter(("OBJECT_ID"<=108 AND "OBJECT_ID">=103))

Column Projection Information (identified by operation id):
-----------------------------------------------------------

   1 - "OBJECT_NAME"[VARCHAR2,128], "OBJECT_ID"[NUMBER,22]

Note
-----
   - SQL plan baseline SQL_PLAN_bhbr0xckgpxbb2dae97d6 used for this statement

从上述测试可以看出,我们可以轻易地在目标SQL的多个执行计划中切换,所以SPM确实是既能够主动地稳定执行计划,又保留了继续使用新的执行计划的机会,并且我们很容易就能启用新的执行计划。
 

你可能感兴趣的:(oracle优化学习)