由于各种原因(如统计信息不准确,CBO成本计算公式缺陷)导致CBO产生效率不高、甚至错误的执行计划。
某个SQL原先跑的好好的,为什么突然慢的让人无法接受,这种效率衰减往往是因为目标SQL执行计划的改变。
1. 重新收集统计信息,但有时不能解决问题
2. 修改目标SQL(比如在目标SQL中加入Hint)文本,第三方软件,不能修改源码
3. 使用SQL Profile(10g以上)或SPM(SQL Plan Management)(11g以上)
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启用。
1. 自动捕获
2. 手工生成/批量导入(批量导入尤其适用于数据库大版本的升级,可以确保升级后原有系统所有SQL的执行计划不会发生改变)
参数 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
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);
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
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
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
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
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所对应的执行计划。
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
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确实是既能够主动地稳定执行计划,又保留了继续使用新的执行计划的机会,并且我们很容易就能启用新的执行计划。