SQL Plan Management如何工作?
当一个SQL硬解析时,基于成本的优化器CBO会生成多个执行计划,并从这些执行计划中选择一个优化器认为最低成本的执行计划。
如果SQL plan baseline生效,优化器会将刚刚生成的执行计划与SQL plan baseline中的执行计划进行比较。
如果该执行计划和基线中的执行计划匹配,且基线中的执行计划标记为可接受(accepted = yes),那么该执行计划将被使用;
如果基线中不存在标记为可接受的执行计划与刚刚生成的执行计划匹配,优化器将评估标记为基线中的可接受的执行计划与当前刚刚生成的执行计划,并选择成本最低的一个。
如果优化器生成的执行计划比当前在基线中的执行计划成本要低,则该执行计划将被添加到基线当中,并标记为一个不可接受的(accepted = no)执行计划,该执行计划不会被直接使用,直到该计划被确认不会引起性能降低。
如果系统变更影响到了所有的可接受的计划,系统会默认这些受影响的计划不可重用,优化器将会使用最低成本的执行计划。
Oracle称这种行为为『保守的计划选择策略』,也即优化器更喜欢选择一个经过测试的执行计划,尽管可能存在一个新的执行计划比当前的执行计划看起来会更加高效。除非这个新的执行计划被证明是更加高效并且标记为可接受的。
SQL plan baseline的使用由OPTIMIZER_USE_SQL_PLAN_BASELINES参数控制,该参数值默认被设置为TRUE,即为使用。访问DBMS_SPM包需要ADMINISTER SQL MANAGEMENT OBJECT权限。
自动捕获
系统是否自动捕获SQL的执行计划基线,是由参数OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES控制,其默认值为FALSE。当该值设置为TRUE的时候,系统将记录SQL语句的执行计划。一个语句的第一个执行计划将被自动标记为可接受的(accepted = yes)。在此之后生成的执行计划将无法呗直接使用,除非被验证不会引起性能降级。性能可被接受的执行计划在演化阶段被添加到执行计划基线当中。
SQL> SHOW PARAMETER OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ optimizer_capture_sql_plan_baselines boolean FALSE SQL> ALTER SYSTEM SET OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES=TRUE; System altered. SQL> select SQL_TEXT,CREATED,ACCEPTED,ENABLED,FIXED,REPRODUCED,AUTOPURGE from dba_sql_plan_baselines; SQL_TEXT CREATED ACC ENA FIX REP AUT -------------------------------------------------------------------------------- ------------------------------ --- --- --- --- --- select count(*) from dba_sql_plan_baselines 08-APR-17 02.16.22.000000 PM YES YES NO YES YES SELECT SYS.DBMS_ASSERT.SIMPLE_SQL_NAME(PARAMETER_VALUE) FROM MGMT_PARAMETERS WHE 08-APR-17 02.18.02.000000 PM YES YES NO YES YES DELETE FROM MGMT_JOB_HISTORY H WHERE STEP_ID = :B1 AND NOT EXISTS (SELECT 1 FROM 08-APR-17 02.17.02.000000 PM YES YES NO YES YES UPDATE MGMT_JOB_EXECUTION SET STEP_STATUS = :B3 WHERE STEP_STATUS = :B2 AND STEP 08-APR-17 02.17.02.000000 PM YES YES NO YES YES UPDATE MGMT_CURRENT_METRICS SET COLLECTION_TIMESTAMP = :B1 , VALUE = :B6 , STRIN 08-APR-17 02.18.02.000000 PM YES YES NO YES YES SELECT COUNT(*) FROM MGMT_PARAMETERS WHERE PARAMETER_NAME=:B1 AND UPPER(PARAMETE 08-APR-17 02.17.02.000000 PM YES YES NO YES YES select SQL_TEXT,CREATED,ACCEPTED,ENABLED,FIXED,REPRODUCED,AUTOPURGE from dba_sql 08-APR-17 02.17.10.000000 PM YES YES NO YES YES SELECT CONTEXT_TYPE_ID,CONTEXT_TYPE,TRACE_LEVEL,NULL,NULL FROM EMDW_TRACE_CONFIG 08-APR-17 02.17.02.000000 PM YES YES NO YES YES SELECT PARAMETER_VALUE FROM MGMT_PARAMETERS WHERE PARAMETER_NAME = :B1 08-APR-17 02.16.02.000000 PM YES YES NO YES YES SELECT target_guid FROM mgmt_metric_dependency WHERE can_calculate = 1 AND 08-APR-17 02.17.02.000000 PM YES YES NO YES YES SELECT UPPER(PARAMETER_VALUE) FROM MGMT_PARAMETERS WHERE PARAMETER_NAME = :B1 08-APR-17 02.16.02.000000 PM YES YES NO YES YES SELECT VALUE V FROM WWV_FLOW_PLATFORM_PREFS WHERE NAME = :B1 08-APR-17 02.18.02.000000 PM YES YES NO YES YES SELECT DISTINCT METRIC_GUID FROM MGMT_METRICS WHERE TARGET_TYPE = :B3 AND METRIC 08-APR-17 02.18.02.000000 PM YES YES NO YES YES SELECT TO_NUMBER(PARAMETER_VALUE) FROM MGMT_PARAMETERS WHERE PARAMETER_NAME = :B 08-APR-17 02.17.02.000000 PM YES YES NO YES YES SELECT NVL(ABS(PARAMETER_VALUE),60) FROM MGMT_PARAMETERS WHERE PARAMETER_NAME = 08-APR-17 02.18.02.000000 PM YES YES NO YES YES SELECT COUNT(FAILOVER_ID) FROM MGMT_FAILOVER_TABLE WHERE SYSDATE-LAST_TIME_STAMP 08-APR-17 02.17.02.000000 PM YES YES NO YES YES SELECT TARGET_GUID FROM MGMT_TARGETS WHERE TARGET_NAME = :B2 AND TARGET_TYPE = : 08-APR-17 02.18.02.000000 PM YES YES NO YES YES SQL> ALTER SYSTEM SET OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES=FALSE; System altered.
不建议在生产环境中开启自动捕获。
除了在SYSTEM级别中更改该参数之外,还可以在SESSION级别中更改。
自动捕获方式原理是 第一次sql语句执行 先查看数据字典看有对应的sqlplan baseline没(用签名查找),没有的话查看一个log(log中有签名,所谓的签名就是将sql语句格式化后的一个标记,可以不分大小写空格之类),没有的话在log中根据sql_text生成一个签 名。第2次执行SQL语句时候 还是先看有对应的sqlplan baselines没,没有的话查看log中有对应的签名没,有的话(此时已经有了),此时才存入sql plan baselines(这个时候存入的执行计划 ,就是你sql语句此时此刻的执行计划,根据统计信息之类算出来的)。所以这个方式为一个sql语句生成sqlplan baseline需要执行2次sql。
如以下示例:
Session 1:
SQL> select sql_handle,SQL_TEXT,ENABLED,ACCEPTED,FIXED from dba_sql_plan_baselines; no rows selected
此时会话1中,无基线存在。
Session 2:
SQL> alter session set OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES=TRUE; Session altered. SQL> select * from emp where empno=7369; EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO ---------- ---------- --------- ---------- --------- ---------- ---------- ---------- 7369 SMITH CLERK 7902 17-DEC-80 800 20
会话2中打开自动捕获,并第一次执行我们的语句。
Session 1:
SQL> select sql_handle,SQL_TEXT,ENABLED,ACCEPTED,FIXED from dba_sql_plan_baselines; no rows selected
此时会话1中,仍无基线存在。
Session 2:
SQL> select * from emp where empno=7369; EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO ---------- ---------- --------- ---------- --------- ---------- ---------- ---------- 7369 SMITH CLERK 7902 17-DEC-80 800 20
会话2中,再次执行语句。
Session 1:
SQL> select sql_handle,SQL_TEXT,ENABLED,ACCEPTED,FIXED from dba_sql_plan_baselines; SQL_HANDLE SQL_TEXT ENA ACC FIX ------------------------------ ---------------------------------------- --- --- --- SQL_353e8c17a551f70c select * from emp where empno=7369 YES YES NO
此时,该会话中可以发现已经捕获了我们执行的SQL语句。
手工加载
手工加载即可以配合自动捕获使用,也可以作为独立的手段使用。加载操作我们用DBMS_SPM包进行操作,既可以从SQL TUNING SETS中加载也可以从Cursor Cache中加载特定的语句。手工加载的被默认标记为可接受的(accepted=yes)。如果该语句已经在基线中存在,则该计划将被添加到执行计划中,否则将新建。
下面的代码演示了使用LOAD_PLANS_FROM_SQLSET函数加载在STS中的SQL语句。加载的过程中可以指定过滤条件。
SET serveroutput ON DECLARE my_int pls_integer; BEGIN my_int := dbms_spm.load_plans_from_sqlset ( sqlset_name => 'b8rc6j0krxwdc_sqlset_test', basic_filter => 'sql_id="b8rc6j0krxwdc"', sqlset_owner => 'SYS', fixed => 'NO', enabled => 'YES'); DBMS_OUTPUT.PUT_line(my_int); END; /
函数LOAD_PLANS_FROM_CURSOR_CACHE则允许从Cursor Cache进行加载。该函数存在4个重载,可以通过多个条件进行定位,比如SQL_ID,SQL_TEXT,PARSING_SCHEMA_NAME,MODULE和ACTION等。
下面的代码示例演示如何加载:
DECLARE l_plans_loaded PLS_INTEGER; BEGIN l_plans_loaded := DBMS_SPM.load_plans_from_cursor_cache( sql_id => '12mf9db6pxnmd',PLAN_HASH_VALUE => '1608914532'); END; /
上述代码中演示为指定的SQL_ID和指定的执行计划进行加载。也可以不指定执行计划,那将加载该语句所有的执行计划。
可以注意到的是LOAD_PLANS_FROM_SQLSET和LOAD_PLANS_FROM_CURSOR_CACHE都有一个返回值,该返回值的意思为共加载了多少个执行计划。
显示SQL执行计划基线中的执行计划
视图DBA_SQL_PLAN_BASELINES只存了计划基线的一般信息,而完整的执行计划信息查看则需要通过DBMS_XPLAN包进行查询。
函数DISPLAY_SQL_PLAN_BASELINE将返回格式化好的信息,可以指定一个或者全部的执行计划,共存在3种格式化模式:BASIC、TYPICAL和ALL。
下面演示了之前自动捕获的基线中的执行计划:
SET LONG 10000
SQL> select sql_handle,PLAN_NAME,ENABLED,ACCEPTED,FIXED from dba_sql_plan_baselines;
SQL_HANDLE PLAN_NAME ENA ACC FIX
------------------------------ ------------------------------ --- --- ---
SQL_353e8c17a551f70c SQL_PLAN_3agnc2ykp3xsc695cc014 YES YES NO
SQL> SELECT * FROM TABLE(DBMS_XPLAN.display_sql_plan_baseline(SQL_HANDLE => 'SQL_353e8c17a551f70c', plan_name=>'SQL_PLAN_3agnc2ykp3xsc695cc014', FORMAT => 'ALL'));
--------------------------------------------------------------------------------
SQL handle: SQL_353e8c17a551f70c
SQL text: select * from emp where empno=7369
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Plan name: SQL_PLAN_3agnc2ykp3xsc695cc014 Plan id: 1767686164
Enabled: YES Fixed: NO Accepted: YES Origin: AUTO-CAPTURE
--------------------------------------------------------------------------------
Plan hash value: 2949544139
--------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 38 | 1 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 38 | 1 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | PK_EMP | 1 | | 0 (0)| 00:00:01 |
--------------------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
1 - SEL$1 / EMP@SEL$1
2 - SEL$1 / EMP@SEL$1
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("EMPNO"=7369)
Column Projection Information (identified by operation id):
-----------------------------------------------------------
1 - "EMPNO"[NUMBER,22], "EMP"."ENAME"[VARCHAR2,10],
"EMP"."JOB"[VARCHAR2,9], "EMP"."MGR"[NUMBER,22], "EMP"."HIREDATE"[DATE,7],
"EMP"."SAL"[NUMBER,22], "EMP"."COMM"[NUMBER,22], "EMP"."DEPTNO"[NUMBER,22]
2 - "EMP".ROWID[ROWID,10], "EMPNO"[NUMBER,22]
更改执行计划基线
ALTER_SQL_PLAN_BASELINE函数提供了更改基线内执行计划的属性的方法。
属性有如下几个:
- enabled(YES/NO):值为YES时,执行计划对优化器来说是可用的,真正被使用时他的另外的属性要被标记为accepted。
- fixed(YES/NO):值为YES时,基线将不会随时间进行演化。相对于非Fixed属性的执行计划,优化器更加青睐有Fixed的属性的执行计划。
- autopurge(YES/NO):值为YES时,如果该执行计划一段时间未被使用,将被自动清除。
- plan_name:用于重命名基线中的sql_plan_name,最多30个字符。
- description:用于修改基线中执行计划的描述信息,最多30个字符。
如下演示了如何将一个执行计划的属性fixed标记为YES。
SQL> select SQL_HANDLE,PLAN_NAME,DESCRIPTION,ENABLED,ACCEPTED,FIXED,AUTOPURGE from dba_sql_plan_baselines; SQL_HANDLE PLAN_NAME DESCRIPTION ENA ACC FIX AUT ------------------------------ ------------------------------ ------------------------------ --- --- --- --- SQL_353e8c17a551f70c SQL_PLAN_3agnc2ykp3xsc695cc014 YES YES NO YES SQL> SQL> SET SERVEROUTPUT ON SQL> DECLARE 2 l_plans_altered PLS_INTEGER; 3 BEGIN 4 l_plans_altered := DBMS_SPM.alter_sql_plan_baseline( 5 sql_handle => 'SQL_353e8c17a551f70c', 6 plan_name => 'SQL_PLAN_3agnc2ykp3xsc695cc014', 7 attribute_name => 'fixed', 8 attribute_value => 'YES'); 9 10 DBMS_OUTPUT.put_line('Plans Altered: ' || l_plans_altered); 11 END; 12 / Plans Altered: 1 PL/SQL procedure successfully completed. SQL> select SQL_HANDLE,PLAN_NAME,DESCRIPTION,ENABLED,ACCEPTED,FIXED,AUTOPURGE from dba_sql_plan_baselines; SQL_HANDLE PLAN_NAME DESCRIPTION ENA ACC FIX AUT ------------------------------ ------------------------------ ------------------------------ --- --- --- --- SQL_353e8c17a551f70c SQL_PLAN_3agnc2ykp3xsc695cc014 YES YES YES YES
配置SPM
SPM的库存放于SYSAUX表空间,存储了SQL PLAN BASELINES,以及语句LOG、计划历史和SQL PROFILE。其空间的使用有两个键值对属性控制,可以通过DBMS_SPM.CONFIGURE进行配置管理。
参数如下:
- space_budget_percent(10):占用SYSAUX表空间的最大使用量。百分比,值允许的范围为1-50.
- plan_retention_weeks(53):那些无用的执行计划在保留多久之后会被清除掉。允许的值范围为5-523,单位周。
其配置可以通过视图DBA_SQL_MANAGEMENT_CONFIG进行查询。
下面的代码演示如何更改配置参数:
SQL> select * from dba_sql_management_config; PARAMETER_NAME PARAMETER_VALUE LAST_MODIFIED MODIFIED_BY ------------------------------ --------------- ------------------------------ ------------------------------ SPACE_BUDGET_PERCENT 10 PLAN_RETENTION_WEEKS 53 SQL> show user USER is "SYS" SQL> BEGIN 2 DBMS_SPM.configure('space_budget_percent', 11); 3 DBMS_SPM.configure('plan_retention_weeks', 54); 4 END; 5 / PL/SQL procedure successfully completed. SQL> select * from dba_sql_management_config; PARAMETER_NAME PARAMETER_VALUE LAST_MODIFIED MODIFIED_BY ------------------------------ --------------- ------------------------------ ------------------------------ SPACE_BUDGET_PERCENT 11 2017-04-08 16:50:12 SYS PLAN_RETENTION_WEEKS 54 2017-04-08 16:50:12 SYS
删除计划基线
DROP_SQL_PLAN_BASELINE函数可以删除一个或者多个执行计划,如果未指定plan name的时候,将删除所有的对应执行计划。
示例如下:
SQL> select SQL_HANDLE,PLAN_NAME,DESCRIPTION,ENABLED,ACCEPTED,FIXED,AUTOPURGE from dba_sql_plan_baselines; SQL_HANDLE PLAN_NAME DESCRIPTION ENA ACC FIX AUT ------------------------------ ------------------------------ ------------------------------ --- --- --- --- SQL_353e8c17a551f70c SYK SPM DEMO YES YES YES YES SQL> SET SERVEROUTPUT ON SQL> DECLARE 2 l_plans_dropped PLS_INTEGER; 3 BEGIN 4 l_plans_dropped := DBMS_SPM.drop_sql_plan_baseline ( 5 sql_handle => 'SQL_353e8c17a551f70c', 6 plan_name => 'SYK SPM DEMO'); 7 8 DBMS_OUTPUT.put_line(l_plans_dropped); 9 END; 10 / 1 PL/SQL procedure successfully completed. SQL> select SQL_HANDLE,PLAN_NAME,DESCRIPTION,ENABLED,ACCEPTED,FIXED,AUTOPURGE from dba_sql_plan_baselines; no rows selected