Oracle SQL Tuning_稳固执行计划04_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. 自动捕获  参考文章 https://blog.csdn.net/u010692693/article/details/102989034
2. 手工生成/批量导入(批量导入尤其适用于数据库大版本的升级,可以确保升级后原有系统所有SQL的执行计划不会发生改变)

手工生成 SQL Plan Baseline

手工生成 SQL Plan Baseline,其核心就是调用 DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE
这里不讨论批量导入,只讨论针对单个SQL的SQL Plan Baseline的手工生成。

参考文章 https://blog.csdn.net/u010692693/article/details/102989034
使用过 Manual 类型的 SQL Profile 可以在不改变目标SQL的SQL文本的情况下调整其执行计划。实际上,用手工生成SQL Plan Baseline的方式也完全可以实现同样的目的,甚至会比使用 Manual 类型的 SQL Profile 更加简洁。

SPM手工生成 SQL Plan Baseline固定执行计划具体步骤:

1. 针对目标SQL使用 DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE 手工生成其初始执行计划所对应的 SQL Plan Baseline。

dbms_spm.load_plans_from_cursor_cache
(
sql_id => '原目标SQL的SQL_ID',
plan_hash_value => 原目标SQL的PLAN HASH VALUE
)

2. 改写原目标SQL的SQL文本,在其中加入合适的 Hint,直到加入 Hint 后的SQL能走出我们想要的执行计划,然后对改写后的SQL使用 DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE 手工生成新的执行计划所对应的 SQL Plan Baseline。

dbms_spm.load_plans_from_cursor_cache
(
sql_id => '加入合适Hint后改写SQL的SQL_ID',
plan_hash_value => 加入合适Hint后改写SQL的PLAN HASH VALUE,
sql_handle => '原目标SQL在步骤(1)中所产生的SQL Plan Baseline的sql_handle'
)

3. 使用 DBMS_SPM.DROP_SQL_PLAN_BASELINE 删除在步骤(1)中手工生成的原目标SQL的初始执行计划所对应的SQL Plan Baseline。

dbms_spm.drop_sql_plan_baseline
(
sql_handle => '原目标SQL在步骤(1)中所产生的SQL Plan Baseline的sql_handle'
plan_name => '原目标SQL在步骤(1)中所产生的SQL Plan Baseline的plan_name'
)

利用手工生成 SQL Plan Baseline,在不改变目标SQL的SQL文本的情况下更改其执行计划的示例

创建测试环境

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

执行如下SQL(这里使用Hint强制不使用索引IDX_T4,以模拟执行计划错误)

select /*+ no_index(t4 idx_t4)*/object_name, object_id from t4 where object_id=4;

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

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------
SQL_ID  160hymtmn0hn2, child number 0
-------------------------------------
select /*+ no_index(t4 idx_t4)*/object_name, object_id from t4 where
object_id=4

Plan hash value: 2560505625

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

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

   1 - SEL$1 / T4@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" "T4"@"SEL$1")
      END_OUTLINE_DATA
  */

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

   1 - filter("OBJECT_ID"=4)

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

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

目标SQL执行计划走TABLE ACCESS FULL,SQL_ID 是 160hymtmn0hn2,PLAN HASH VALUE 是 2560505625

没有开启 SQL Plan Baseline 的自动捕获,如下查询没有记录,目标SQL没有对应的 SQL Plan Baseline

select sql_handle, plan_name, origin, enabled, accepted, sql_text from dba_sql_plan_baselines where sql_text like 'select /*+ no_index(t4 idx_t4)*/object_name%';

no rows selected

使用目标SQL的初始执行计划(TABLE ACCESS FULL)所对应的 SQL_ID 和 PLAN HASH VALUE 来手工生成对应的 SQL Plan Baseline

var temp number
exec :temp := dbms_spm.load_plans_from_cursor_cache(sql_id => '160hymtmn0hn2', plan_hash_value => 2560505625);

如下查询,目标SQL的初始执行计划所对应的 SQL Plan Baseline 已经成功生成
select sql_handle, plan_name, origin, enabled, accepted, sql_text from dba_sql_plan_baselines where sql_text like 'select /*+ no_index(t4 idx_t4)*/object_name%';

SQL_HANDLE                     PLAN_NAME                      ORIGIN         ENA ACC SQL_TEXT
------------------------------ ------------------------------ -------------- --- --- ----------------------------------------------------------------------------------------------------
SQL_eace6500cb832b55           SQL_PLAN_fpmm5035s6aupcda915e5 MANUAL-LOAD    YES YES select /*+ no_index(t4 idx_t4)*/object_name, object_id from t4 where object_id=4

改写原目标SQL,加入强制走索引IDX_T4的Hint后重新执行

select /*+ index(t4 idx_t4)*/object_name, object_id from t4 where object_id=4;

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

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------
SQL_ID  92h09bz9kkr2u, child number 0
-------------------------------------
select /*+ index(t4 idx_t4)*/object_name, object_id from t4 where
object_id=4

Plan hash value: 1884632991

--------------------------------------------------------------------------------------
| Id  | Operation                   | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |        |       |       |     2 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| T4     |     1 |    30 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | IDX_T4 |     1 |       |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------------------

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

   1 - SEL$1 / T4@SEL$1
   2 - SEL$1 / T4@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" "T4"@"SEL$1" ("T4"."OBJECT_ID"))
      END_OUTLINE_DATA
  */

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

   2 - access("OBJECT_ID"=4)

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

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

该SQL的执行计划从 TABLE ACCESS FULL 变为 INDEX RANGE SCAN,改写后的SQL其 SQL_ID 是 92h09bz9kkr2u,PLAN HASH VALUE 是 1884632991

SQL> select sql_handle, plan_name, origin, enabled, accepted, sql_text from dba_sql_plan_baselines where sql_text like 'select /*+ no_index(t4 idx_t4)*/object_name%';

SQL_HANDLE                     PLAN_NAME                      ORIGIN         ENA ACC SQL_TEXT
------------------------------ ------------------------------ -------------- --- --- ----------------------------------------------------------------------------------------------------
SQL_eace6500cb832b55           SQL_PLAN_fpmm5035s6aupcda915e5 MANUAL-LOAD    YES YES select /*+ no_index(t4 idx_t4)*/object_name, object_id from t4 where object_id=4

用改写后的SQL的新的执行计划(INDEX RANGE SCAN)所对应的 SQL_ID 和 PLAN HASH VALUE 以及原目标SQL的 SQL Plan Baseline 的 sql_handle 来手工生成新的 SQL Plan Baseline:

exec :temp := dbms_spm.load_plans_from_cursor_cache(sql_id => '92h09bz9kkr2u', plan_hash_value => 1884632991, sql_handle => 'SQL_eace6500cb832b55');

如下查询,改写后的SQL的新执行计划所对应的 SQL Plan Baseline 已经成功生成,ENABLED 和 ACCEPTED 的值均为“YES”

SQL> select sql_handle, plan_name, origin, enabled, accepted, sql_text from dba_sql_plan_baselines where sql_text like 'select /*+ no_index(t4 idx_t4)*/object_name%';

SQL_HANDLE                     PLAN_NAME                      ORIGIN         ENA ACC SQL_TEXT
------------------------------ ------------------------------ -------------- --- --- ----------------------------------------------------------------------------------------------------
SQL_eace6500cb832b55           SQL_PLAN_fpmm5035s6aup52d9ffe2 MANUAL-LOAD    YES YES select /*+ no_index(t4 idx_t4)*/object_name, object_id from t4 where object_id=4
SQL_eace6500cb832b55           SQL_PLAN_fpmm5035s6aupcda915e5 MANUAL-LOAD    YES YES select /*+ no_index(t4 idx_t4)*/object_name, object_id from t4 where object_id=4

DROP掉原执行计划(TABLE ACCESS FULL)所对应的 SQL Plan Baseline:

exec :temp := dbms_spm.drop_sql_plan_baseline(sql_handle => 'SQL_eace6500cb832b55', plan_name => 'SQL_PLAN_fpmm5035s6aupcda915e5');

SQL> select sql_handle, plan_name, origin, enabled, accepted, sql_text from dba_sql_plan_baselines where sql_text like 'select /*+ no_index(t4 idx_t4)*/object_name%';

SQL_HANDLE                     PLAN_NAME                      ORIGIN         ENA ACC SQL_TEXT
------------------------------ ------------------------------ -------------- --- --- ----------------------------------------------------------------------------------------------------
SQL_eace6500cb832b55           SQL_PLAN_fpmm5035s6aup52d9ffe2 MANUAL-LOAD    YES YES select /*+ no_index(t4 idx_t4)*/object_name, object_id from t4 where object_id=4

再次执行查询,执行计划 已经从 TABLE ACCESS FULL 变为 INDEX RANGE SCAN

select /*+ no_index(t4 idx_t4)*/object_name, object_id from t4 where object_id=4;

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

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------
SQL_ID  160hymtmn0hn2, child number 2
-------------------------------------
select /*+ no_index(t4 idx_t4)*/object_name, object_id from t4 where
object_id=4

Plan hash value: 1884632991

--------------------------------------------------------------------------------------
| Id  | Operation                   | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |        |       |       |     2 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| T4     |     1 |    30 |     2   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | IDX_T4 |     1 |       |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------------------

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

   1 - SEL$1 / T4@SEL$1
   2 - SEL$1 / T4@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" "T4"@"SEL$1" ("T4"."OBJECT_ID"))
      END_OUTLINE_DATA
  */

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

   2 - access("OBJECT_ID"=4)

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

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

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

 

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