一、SQL Profile

分为两种:自动(Automatic)类型的SQL Profile、手动(Manual)类型的SQL Profile

1.自动(Automatic)类型的SQL Profile

1.1.创建测试表T1

SQL> create table t1 (id number);

Table created.

SQL> declare
begin
for i in 1 .. 10000
loop
insert into t1 values(i);
commit;
end loop;
end;
/

PL/SQL procedure successfully completed.

1.2.创建索引

SQL> create index idx_t1 on t1(id);

Index created.

1.3.对t1表收集统计信息

SQL> exec dbms_stats.gather_table_stats(ownname=>'TEST',tabname=>'T1',estimate_percent=>DBMS_STATS.AUTO_SAMPLE_SIZE,degree=>2,cascade=>true,granularity=>'ALL');

PL/SQL procedure successfully completed.

1.4.使查询不走索引,模拟错误的执行计划

SQL> select /*+ no_index(t1 idx_t1) */ * from t1 where id=1;

	ID
----------
	 1

--查看上面sql的执行计划
SQL> explain plan for  select /*+ no_index(t1 idx_t1) */ * from t1 where id=1;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation	      | Name | Rows  | Bytes | Cost (%CPU)| Time	 |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |	     |     1 |     4 |     7   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T1	 |     1 |     4 |     7   (0)| 00:00:01 |
--------------------------------------------------------------------------

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

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

   1 - filter("ID"=1)

此时可以看到执行计划是走全表扫描的,而不是走索引(idx_t1)范围扫描,是一个错误的执行计划。

1.5.通过使用sql调优顾问,对sql生成自动类型的SQL Profile

1.5.1首先创建一个自动调优任务
SQL> declare
my_task_name varchar2(30);
my_sqltext CLOB;
begin
my_sqltext := 'select /*+ no_index(t1 idx_t1) */ * from t1 where id=1';
my_task_name := dbms_sqltune.create_tuning_task(
sql_text => my_sqltext,
user_name => 'TEST',
scope => 'COMPREHENSIVE',
time_limit => 60,
task_name => 'my_sql_tuning_2',
description => 'task to tune a query on table t1');
end;
/

PL/SQL procedure successfully completed.
1.5.2.执行自动调优任务
SQL> begin
dbms_sqltune.execute_tuning_task(task_name => 'my_sql_tuning_2');
end;
/

PL/SQL procedure successfully completed.
1.5.3.查看自动调优任务的结果
SQL> select dbms_sqltune.report_tuning_task(task_name => 'my_sql_tuning_2') from dual;

GENERAL INFORMATION SECTION
-------------------------------------------------------------------------------
Tuning Task Name   : my_sql_tuning_2
Tuning Task Owner  : TEST
Workload Type      : Single SQL Statement
Execution Count    : 3
Current Execution  : EXEC_1254
Execution Type     : TUNE SQL
Scope              : COMPREHENSIVE
Time Limit(seconds): 60
Completion Status  : COMPLETED
Started at         : 03/31/2023 15:13:20
Completed at       : 03/31/2023 15:13:20

-------------------------------------------------------------------------------
Schema Name: TEST
SQL ID     : fqrvjn6x6kdmb
SQL Text   : select /*+ no_index(t1 idx_t1) */ * from t1 where id=1

-------------------------------------------------------------------------------
FINDINGS SECTION (1 finding)
-------------------------------------------------------------------------------

1- SQL Profile Finding (see explain plans section below)
--------------------------------------------------------
  A potentially better execution plan was found for this statement.

  Recommendation (estimated benefit: 90.91%)  --提供了一个配置SQL Profile的建议,是一个比原始sql更好的执行计划。
  ------------------------------------------
  - Consider accepting the recommended SQL profile.
    execute dbms_sqltune.accept_sql_profile(task_name => 'my_sql_tuning_2',
            task_owner => 'TEST', replace => TRUE);


                           Original Plan  With SQL Profile  % Improved
                           -------------  ----------------  ----------
  Completion Status:            COMPLETE          COMPLETE
  Elapsed Time (s):             .000074           .000007      90.54 %
  CPU Time (s):                  .00005                 0        100 %
  User I/O Time (s):                  0                 0 
  Buffer Gets:                       22                 2       90.9 %
  Physical Read Requests:             0                 0 
  Physical Write Requests:            0                 0 
  Physical Read Bytes:                0                 0 
  Physical Write Bytes:               0                 0 
  Rows Processed:                     1                 1 
  Fetches:                            1                 1 
  Executions:                         1                 1 

-------------------------------------------------------------------------------
EXPLAIN PLANS SECTION
-------------------------------------------------------------------------------

1- Original With Adjusted Cost
------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |     4 |     7   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T1   |     1 |     4 |     7   (0)| 00:00:01 |
--------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter("ID"=1)

2- Using SQL Profile
--------------------
Plan hash value: 1369807930

---------------------------------------------------------------------------
| Id  | Operation        | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT |        |     1 |     4 |     1   (0)| 00:00:01 |
|*  1 |  INDEX RANGE SCAN| IDX_T1 |     1 |     4 |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - access("ID"=1)
1.5.4.接受并使用上面的SQL Profile
SQL> execute dbms_sqltune.accept_sql_profile(task_name => 'my_sql_tuning_2',task_owner => 'TEST', replace => TRUE);

PL/SQL procedure successfully completed.

--注:执行accept_sql_profile可能会遇到以下报错:
ERROR at line 1:
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at "SYS.DBMS_SQLTUNE_INTERNAL", line 16459

--解决方法
删除调优任务,按照之前步骤重新创建并运行调优任务:
SQL> exec DBMS_SQLTUNE.DROP_TUNING_TASK('<task_name>');

PL/SQL procedure successfully completed.
1.5.5.再次执行查询,查看SQL Profile效果
SQL> explain plan for select /*+ no_index(t1 idx_t1) */ * from t1 where id=1;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------------
Plan hash value: 1369807930

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

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

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------
   1 - access("ID"=1)

Note
-----
   - SQL profile "SYS_SQLPROF_018736e035950000" used for this statement  ---说明SQL profile固定执行计划生效
1.5.6.修改where条件 where id=2 测试SQL profile固定执行计划是否可用
SQL> explain plan for  select /*+ no_index(t1 idx_t1) */ * from t1 where id=2;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation	      | Name | Rows  | Bytes | Cost (%CPU)| Time	 |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |	     |     1 |     4 |     7   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T1	 |     1 |     4 |     7   (0)| 00:00:01 |
--------------------------------------------------------------------------

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

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------

   1 - filter("ID"=2)
   
此时可以看到执行计划是走全表扫描的,而不是走索引(idx_t1)范围扫描,说明SQL profile固定执行计划失效了。
想使SQL profile对改动后的SQL生效,可以在accept_sql_profile时加入force_match=true参数。
1.5.7.重新执行dbms_sqltune.accept_sql_profile,添加参数 force_match=true
SQL> execute dbms_sqltune.accept_sql_profile(task_name => 'my_sql_tuning_2',task_owner => 'TEST', replace => TRUE,force_match => TRUE);

PL/SQL procedure successfully completed.

--查看执行计划
SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------
Plan hash value: 1369807930

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

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

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------

   1 - access("ID"=3)

Note
-----
   - SQL profile "SYS_SQLPROF_018736f4009e0001" used for this statement

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------
Plan hash value: 1369807930

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

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

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------

   1 - access("ID"=4)

Note
-----
   - SQL profile "SYS_SQLPROF_018736f4009e0001" used for this statement
   
通过上面执行计划可以看出,不论是将条件改成 where=3 还是 where=4,所对应的SQL profile依然有效。

1.6.删除SQL profile

select NAME,SQL_TEXT from dba_sql_profiles;
exec dbms_sqltune.drop_sql_profile(name => 'SYS_SQLPROF_018736e035950000');

2.手动(Manual)类型的SQL Profile

2.1.查看当前执行计划

SQL> explain plan for select /*+ no_index(t1 idx_t1) */ * from t1 where id=3;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation	      | Name | Rows  | Bytes | Cost (%CPU)| Time	 |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |	     |     1 |     4 |     7   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T1	 |     1 |     4 |     7   (0)| 00:00:01 |
--------------------------------------------------------------------------

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

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

   1 - filter("ID"=3)

2.2.强制索引后的执行计划

SQL> explain plan for select /*+ index(t1 idx_t1) */ * from t1 where id=3;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------
Plan hash value: 1369807930

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

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

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

   1 - access("ID"=3)

2.3.查看上述两个SQL的哈希值

SQL> select SQL_TEXT,SQL_ID from v$sqltext where sql_text like 'select %id=3%';

SQL_TEXT							                         SQL_ID   
-------------------------------------------------------- -------------
select /*+ index(t1 idx_t1) */ * from t1 where id=3	     78hmyyr63192n
select /*+ no_index(t1 idx_t1) */ * from t1 where id=3   6y0gdy21h3sp5

SQL> select plan_hash_value from v$sql where sql_id='78hmyyr63192n';

PLAN_HASH_VALUE
---------------
     1369807930

SQL> select plan_hash_value from v$sql where sql_id='6y0gdy21h3sp5';

PLAN_HASH_VALUE
---------------
     3617692013

2.4.执行coe_xfr_sql_profile.sql脚本,过程中需要输入原目标SQL(select /*+ no_index(t1 idx_t1) */ * from t1 where id=3)的sql_id和plan_hash_value

SQL>@/home/oracle/sqlt/utl/coe_xfr_sql_profile.sql

Parameter 1:
SQL_ID (required)

Enter value for 1: 6y0gdy21h3sp5

PLAN_HASH_VALUE AVG_ET_SECS
--------------- -----------
     3617692013        .018

Parameter 2:
PLAN_HASH_VALUE (required)

Enter value for 2: 3617692013

Values passed to coe_xfr_sql_profile:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SQL_ID	       : "6y0gdy21h3sp5"
PLAN_HASH_VALUE: "3617692013"

SQL>BEGIN
  2    IF :sql_text IS NULL THEN
  3  	 RAISE_APPLICATION_ERROR(-20100, 'SQL_TEXT for SQL_ID &&sql_id. was not found in memory (gv$sqltext_with_newlines) or AWR (dba_hist_sqltext).');
  4    END IF;
  5  END;
  6  /
SQL>SET TERM OFF;
SQL>BEGIN
  2    IF :other_xml IS NULL THEN
  3  	 RAISE_APPLICATION_ERROR(-20101, 'PLAN for SQL_ID &&sql_id. and PHV &&plan_hash_value. was not found in memory (gv$sql_plan) or AWR (dba_hist_sql_plan).');
  4    END IF;
  5  END;
  6  /
SQL>SET TERM OFF;

Execute coe_xfr_sql_profile_6y0gdy21h3sp5_3617692013.sql
on TARGET system in order to create a custom SQL Profile
with plan 3617692013 linked to adjusted sql_text.

COE_XFR_SQL_PROFILE completed.

2.5.再次执行coe_xfr_sql_profile.sql脚本,过程中需要输入改写后SQL(select /*+ index(t1 idx_t1) */ * from t1 where id=3)的sql_id和plan_hash_value

SQL>@/home/oracle/sqlt/utl/coe_xfr_sql_profile.sql

Parameter 1:
SQL_ID (required)

Enter value for 1: 78hmyyr63192n


PLAN_HASH_VALUE AVG_ET_SECS
--------------- -----------
     1369807930        .004

Parameter 2:
PLAN_HASH_VALUE (required)

Enter value for 2: 1369807930

Values passed to coe_xfr_sql_profile:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SQL_ID	       : "78hmyyr63192n"
PLAN_HASH_VALUE: "1369807930"

SQL>BEGIN
  2    IF :sql_text IS NULL THEN
  3  	 RAISE_APPLICATION_ERROR(-20100, 'SQL_TEXT for SQL_ID &&sql_id. was not found in memory (gv$sqltext_with_newlines) or AWR (dba_hist_sqltext).');
  4    END IF;
  5  END;
  6  /
SQL>SET TERM OFF;
SQL>BEGIN
  2    IF :other_xml IS NULL THEN
  3  	 RAISE_APPLICATION_ERROR(-20101, 'PLAN for SQL_ID &&sql_id. and PHV &&plan_hash_value. was not found in memory (gv$sql_plan) or AWR (dba_hist_sql_plan).');
  4    END IF;
  5  END;
  6  /
SQL>SET TERM OFF;

Execute coe_xfr_sql_profile_78hmyyr63192n_1369807930.sql
on TARGET system in order to create a custom SQL Profile
with plan 1369807930 linked to adjusted sql_text.

COE_XFR_SQL_PROFILE completed.

注:章节2.42.5生成的两个脚本,默认在/home/oracle目录下。

2.6.将原SQL的Hint部分替换为改写后SQL的Hint部分

-- there is no need to edit or re-align unmodified pieces.
wa(q'[select /*+ no_index(t1 idx_t1) */ * from t1 where id=3]');
DBMS_LOB.CLOSE(sql_txt);
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" "T1"@"SEL$1" ("T1"."ID"))]',
q'[END_OUTLINE_DATA]');

force_match => TRUE 

2.7.替换完成后,执行coe_xfr_sql_profile_6y0gdy21h3sp5_3617692013.sql脚本

SQL>@/home/oracle/coe_xfr_sql_profile_6y0gdy21h3sp5_3617692013.sql
SQL>REM
SQL>REM $Header: 215187.1 coe_xfr_sql_profile_6y0gdy21h3sp5_3617692013.sql 11.4.4.4 2023/04/03 carlos.sierra $
SQL>REM
SQL>REM Copyright (c) 2000-2012, Oracle Corporation. All rights reserved.
SQL>REM
SQL>REM AUTHOR
SQL>REM   carlos.sierra@oracle.com
SQL>REM
SQL>REM SCRIPT
SQL>REM   coe_xfr_sql_profile_6y0gdy21h3sp5_3617692013.sql
SQL>REM
SQL>REM DESCRIPTION
SQL>REM   This script is generated by coe_xfr_sql_profile.sql
SQL>REM   It contains the SQL*Plus commands to create a custom
SQL>REM   SQL Profile for SQL_ID 6y0gdy21h3sp5 based on plan hash
SQL>REM   value 3617692013.
SQL>REM   The custom SQL Profile to be created by this script
SQL>REM   will affect plans for SQL commands with signature
SQL>REM   matching the one for SQL Text below.
SQL>REM   Review SQL Text and adjust accordingly.
SQL>REM
SQL>REM PARAMETERS
SQL>REM   None.
SQL>REM
SQL>REM EXAMPLE
SQL>REM   SQL> START coe_xfr_sql_profile_6y0gdy21h3sp5_3617692013.sql;
SQL>REM
SQL>REM NOTES
SQL>REM   1. Should be run as SYSTEM or SYSDBA.
SQL>REM   2. User must have CREATE ANY SQL PROFILE privilege.
SQL>REM   3. SOURCE and TARGET systems can be the same or similar.
SQL>REM   4. To drop this custom SQL Profile after it has been created:
SQL>REM	 EXEC DBMS_SQLTUNE.DROP_SQL_PROFILE('coe_6y0gdy21h3sp5_3617692013');
SQL>REM   5. Be aware that using DBMS_SQLTUNE requires a license
SQL>REM	 for the Oracle Tuning Pack.
SQL>REM   6. If you modified a SQL putting Hints in order to produce a desired
SQL>REM	 Plan, you can remove the artifical Hints from SQL Text pieces below.
SQL>REM	 By doing so you can create a custom SQL Profile for the original
SQL>REM	 SQL but with the Plan captured from the modified SQL (with Hints).
SQL>REM
SQL>WHENEVER SQLERROR EXIT SQL.SQLCODE;
SQL>REM
SQL>VAR signature NUMBER;
SQL>VAR signaturef NUMBER;
SQL>REM
SQL>DECLARE
  2  sql_txt CLOB;
  3  h	     SYS.SQLPROF_ATTR;
  4  PROCEDURE wa (p_line IN VARCHAR2) IS
  5  BEGIN
  6  DBMS_LOB.WRITEAPPEND(sql_txt, LENGTH(p_line), p_line);
  7  END wa;
  8  BEGIN
  9  DBMS_LOB.CREATETEMPORARY(sql_txt, TRUE);
 10  DBMS_LOB.OPEN(sql_txt, DBMS_LOB.LOB_READWRITE);
 11  -- SQL Text pieces below do not have to be of same length.
 12  -- So if you edit SQL Text (i.e. removing temporary Hints),
 13  -- there is no need to edit or re-align unmodified pieces.
 14  wa(q'[select /*+ no_index(t1 idx_t1) */ * from t1 where id=3]');
 15  DBMS_LOB.CLOSE(sql_txt);
 16  h := SYS.SQLPROF_ATTR(
 17  q'[BEGIN_OUTLINE_DATA]',
 18  q'[IGNORE_OPTIM_EMBEDDED_HINTS]',
 19  q'[OPTIMIZER_FEATURES_ENABLE('11.2.0.4')]',
 20  q'[DB_VERSION('11.2.0.4')]',
 21  q'[ALL_ROWS]',
 22  q'[OUTLINE_LEAF(@"SEL$1")]',
 23  q'[INDEX(@"SEL$1" "T1"@"SEL$1" ("T1"."ID"))]',
 24  q'[END_OUTLINE_DATA]');
 25  :signature := DBMS_SQLTUNE.SQLTEXT_TO_SIGNATURE(sql_txt);
 26  :signaturef := DBMS_SQLTUNE.SQLTEXT_TO_SIGNATURE(sql_txt, TRUE);
 27  DBMS_SQLTUNE.IMPORT_SQL_PROFILE (
 28  sql_text	 => sql_txt,
 29  profile	 => h,
 30  name	 => 'coe_6y0gdy21h3sp5_3617692013',
 31  description => 'coe 6y0gdy21h3sp5 3617692013 '||:signature||' '||:signaturef||'',
 32  category	 => 'DEFAULT',
 33  validate	 => TRUE,
 34  replace	 => TRUE,
 35  force_match => TRUE /* TRUE:FORCE (match even when different literals in SQL). FALSE:EXACT (similar to CURSOR_SHARING) */ );
 36  DBMS_LOB.FREETEMPORARY(sql_txt);
 37  END;
 38  /

PL/SQL procedure successfully completed.

SQL>WHENEVER SQLERROR CONTINUE
SQL>SET ECHO OFF;

	    SIGNATURE
---------------------
 12630920156370535584

	   SIGNATUREF
---------------------
 10266511399854469173

... manual custom SQL Profile has been created

COE_XFR_SQL_PROFILE_6y0gdy21h3sp5_3617692013 completed

2.8.再次执行查询,查看执行计划

SQL>explain plan for select /*+ no_index(t1 idx_t1) */ * from t1 where id=3;

Explained.

SQL>select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 1369807930

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

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

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

   1 - access("ID"=3)

Note
-----
   - SQL profile "coe_6y0gdy21h3sp5_3617692013" used for this statement

也就是说,手动类型的SQL Profile也可以在不改变目标SQLSQL文本的情况下更改其执行计划。
重要的是,手动类型的SQL Profile可以起到很好的稳定SQL执行计划的作用,这一点是自动类型SQL Profile所不具备的。

二、SPM(SQL Plan Management)

SQL Profile实际上是一种被动的技术手段,应用在那些执行计划已经发生了不好的变更的SQL上。
也就是说当SQL语句的执行计划已经出现了问题,通过创建SQL Profile进行解决、稳定这些SQL的执行计划。
这种方式并不能保证后续执行SQL的执行计划不会发生错误的变更。

SPM是一种主动稳定执行计划的手段,能够保证只有被验证过的执行计划才会被启用。
当启用SPM后,每一个SQL都会存在对应的SQL Plan Baseline,里面存储的就是SQL的执行计划。
如果一个SQL有多个执行计划,就会有多个SQL Plan Baseline,可以从视图dba_sql_plan_baselines中查看。
dba_sql_plan_baselines中ENABLED和ACCEPTED列描述一个SQL Plan Baseline所对应的执行计划是否被Oracle启用。
只有上述两列的值都为YES,所对应的执行计划才会被Oracle启用。
存在多个SQL Plan Baseline时,Oracle会从中选择成本值最小的一个,所对应的执行计划来作为该SQL的执行计划。

在Oracle 11g及其以上版本中,有以下两种方法可以产生目标SQL的SQL Plan Baseline:
(1)自动捕获。
(2)手工生成/批量导入。

1.自动捕获

参数optimizer_capture_sql_plan_baselines用于控制是否开启自动捕获。默认值为FALSE。
参数optimizer_use_sql_plan_baselines用于控制是否启用SQL Plan Baseline。默认值为TRUE。
以上两个参数都可以在会话和系统级别动态修改。

1.1.当前会话禁用SPM,并开启自动捕获

SQL> show parameter sql_plan

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

SQL> alter session set optimizer_use_sql_plan_baselines=false;

Session altered.

SQL> alter session set optimizer_capture_sql_plan_baselines=true;

Session altered.

1.2.创建测试表T2,并创建索引

SQL> create table t2 as select * from dba_objects;

Table created.

SQL> create index idx_t2 on t2(object_id);

Index created.

1.3.对表t2收集统计信息

SQL> exec dbms_stats.gather_table_stats(ownname=>'TEST',tabname=>'T2',estimate_percent=>DBMS_STATS.AUTO_SAMPLE_SIZE,degree=>2,cascade=>true,granularity=>'ALL');

PL/SQL procedure successfully completed.

1.4.执行查询,并查看执行计划

SQL> select object_id,object_name from t2 where object_id between 103 and 108;

 OBJECT_ID OBJECT_NAME
---------- ----------------
    103     MIGRATE$
    104     DEPENDENCY$
    105     ACCESS$
    106     I_DEPENDENCY1
    107     I_DEPENDENCY2
    108     I_ACCESS1

SQL> explain plan for select object_id,object_name from t2 where object_id between 103 and 108;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------
Plan hash value: 2008370210

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

Predicate Information (identified by operation id):

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------------

   2 - access("OBJECT_ID">=103 AND "OBJECT_ID"<=108)SQL的执行计划走的是IDX_T2索引范围扫描。

--此时没有捕获到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

1.5.目标SQL重复执行后,Oracle针对执行计划(对索引IDX_T2的索引范围扫描)生成了一个SQL Plan Baseline

SQL> select object_id,object_name from t2 where object_id between 103 and 108;

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_ac526b1e4be74880  SQL_PLAN_asnmb3t5yfk4024c6dbb6  AUTO-CAPTURE  YES YES select object_id,object_name from t2 where object_id between 103 and 108

注:dba_sql_plan_baselines视图常用列解释
SQL_HANDLE:字符串形式的唯一SQL标识符作为搜索关键字
PLAN_NAME:计划名称
ORIGIN:如何创建的计划基线,四种方式,(1)MANUAL-LOAD2)AUTO-CAPTURE(3)MANUAL-SQLTUNE(4)AUTO-SQLTUNE
ENABLED:指示计划基线是否启用
ACCEPTED:指示是否接受计划基线
SQL_TEXT:SQL文本信息

1.6.修改索引聚簇因子值,使其执行计划走全表扫描

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

PL/SQL procedure successfully completed.

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

INDEX_NAME CLUSTERING_FACTOR
---------- -----------------
  IDX_T2	   24000000

1.7.执行查询,并查看执行计划

SQL> explain plan for select object_id,object_name from t2 where object_id between 103 and 108;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------
Plan hash value: 1513984157

--------------------------------------------------------------------------
| Id  | Operation	      | Name | Rows  | Bytes | Cost (%CPU)| Time	 |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |	     |     7 |   210 |   345   (1)| 00:00:05 |
|*  1 |  TABLE ACCESS FULL| T2	 |     7 |   210 |   345   (1)| 00:00:05 |
--------------------------------------------------------------------------

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

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------

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

1.8.产生新的执行计划,并自动捕获产生一个新的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%';

SQL_HANDLE		       PLAN_NAME		                 ORIGIN	    ENA ACC SQL_TEXT
--------------------- ------------------------------ -------------- --- --- -------------------------------------------------------------------------
SQL_ac526b1e4be74880  SQL_PLAN_asnmb3t5yfk4024c6dbb6  AUTO-CAPTURE  YES YES select object_id,object_name from t2 where object_id between 103 and 108
SQL_ac526b1e4be74880  SQL_PLAN_asnmb3t5yfk40b860bcf2  AUTO-CAPTURE  YES NO  select object_id,object_name from t2 where object_id between 103 and 108

1.9.当前会话禁用自动捕获,并开启SPM

SQL> alter session set optimizer_capture_sql_plan_baselines=false;

Session altered.

SQL> alter session set optimizer_use_sql_plan_baselines=true;

Session altered.

1.10.IDX_T2索引的聚簇因子等条件都没有改变,查看执行计划

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

INDEX_NAME CLUSTERING_FACTOR
---------- -----------------
  IDX_T2	   24000000

SQL> explain plan for select object_id,object_name from t2 where object_id between 103 and 108;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------
Plan hash value: 2008370210

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

Predicate Information (identified by operation id):

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------

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

Note
-----
   - SQL plan baseline "SQL_PLAN_asnmb3t5yfk4024c6dbb6" used for this statement

从上述执行计划可以看到,走的是索引范围扫描,也就是SQL_PLAN_asnmb3t5yfk4024c6dbb6这个计划基线(存在IDX_T2索引)。
也就是说Oracle会使用ENABLED、ACCEPTED均为YES的SQL plan baseline作为执行计划。
测试结果可以看出SPM可以固定执行计划。

1.11.启用全表扫描的执行计划

可以使用DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE和DBMS_SPM.ALTER_SQL_PLAN_BASELINE达到启用目标SQL新执行计划的目的。

1.11.1使用DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE将目标SQL新执行计划所对应的SQL plan baseline(SQL_PLAN_asnmb3t5yfk40b860bcf2)的ACCEPTED值更改为YES
SQL> var temp varchar2(1000);
SQL> exec :temp :=dbms_spm.evolve_sql_plan_baseline(sql_handle => 'SQL_ac526b1e4be74880',plan_name => 'SQL_PLAN_asnmb3t5yfk40b860bcf2',verify => 'NO',commit => 'YES');

PL/SQL procedure successfully completed.

--查看ACCEPTED值更改是否成功
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_ac526b1e4be74880  SQL_PLAN_asnmb3t5yfk4024c6dbb6  AUTO-CAPTURE  YES YES select object_id,object_name from t2 where object_id between 103 and 108
SQL_ac526b1e4be74880  SQL_PLAN_asnmb3t5yfk40b860bcf2  AUTO-CAPTURE  YES YES select object_id,object_name from t2 where object_id between 103 and 108
1.11.2.使用DBMS_SPM.ALTER_SQL_PLAN_BASELINE将索引IDX_T2执行计划所对应的SQL plan baseline(SQL_PLAN_asnmb3t5yfk4024c6dbb6)的ENABLED值更改为NO
SQL> exec :temp :=dbms_spm.alter_sql_plan_baseline(sql_handle => 'SQL_ac526b1e4be74880',plan_name => 'SQL_PLAN_asnmb3t5yfk4024c6dbb6',attribute_name => 'ENABLED',attribute_value => 'NO');

PL/SQL procedure successfully completed.

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_ac526b1e4be74880  SQL_PLAN_asnmb3t5yfk4024c6dbb6  AUTO-CAPTURE  NO  YES select object_id,object_name from t2 where object_id between 103 and 108
SQL_ac526b1e4be74880  SQL_PLAN_asnmb3t5yfk40b860bcf2  AUTO-CAPTURE  YES YES select object_id,object_name from t2 where object_id between 103 and 108

--此时的执行计划就使用了全表扫描
SQL> explain plan for select object_id,object_name from t2 where object_id between 103 and 108;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------
Plan hash value: 1513984157

--------------------------------------------------------------------------
| Id  | Operation	      | Name | Rows  | Bytes | Cost (%CPU)| Time	 |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |	     |     7 |   210 |   345   (1)| 00:00:05 |
|*  1 |  TABLE ACCESS FULL| T2	 |     7 |   210 |   345   (1)| 00:00:05 |
--------------------------------------------------------------------------

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

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------

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

Note
-----
   - SQL plan baseline "SQL_PLAN_asnmb3t5yfk40b860bcf2" used for this statement

以此验证了我们可以在目标SQL的多个执行计划中进行切换,所以SPM确实是既能够主动的稳定执行计划,又保留了继续使用新的执行计划的机会。
并且可以很容易的启用新的执行计划。

2.手工生成SQL Plan Baseline

使用DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE手工生成其初始执行计划所对应的SQL plan baseline。

2.1.模拟错误的执行计划

SQL> select /*+ no_index(t2 idx_t2) */object_name,object_id from t2 where object_id=4;

OBJECT_NAME	OBJECT_ID
----------- ---------
TAB$		    4

SQL> explain plan for select /*+ no_index(t2 idx_t2) */object_name,object_id from t2 where object_id=4;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------
Plan hash value: 1513984157

--------------------------------------------------------------------------
| Id  | Operation	      | Name | Rows  | Bytes | Cost (%CPU)| Time	 |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |	     |     1 |    30 |   345   (1)| 00:00:05 |
|*  1 |  TABLE ACCESS FULL| T2	 |     1 |    30 |   345   (1)| 00:00:05 |
--------------------------------------------------------------------------

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

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------

   1 - filter("OBJECT_ID"=4)

SQL> select SQL_ID from v$sqlarea where sql_text='select /*+ no_index(t2 idx_t2) */object_name,object_id from t2 where object_id=4';

SQL_ID
-------------
76td2qkxcrn9v

2.2.使用所对应的sql_id和Plan hash value,手工生成SQL Plan Baseline

SQL> var temp number
SQL> exec :temp :=dbms_spm.load_plans_from_cursor_cache(sql_id =>'76td2qkxcrn9v',plan_hash_value => 1513984157);

PL/SQL procedure successfully completed.

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

SQL_HANDLE		       PLAN_NAME		                ORIGIN	   ENA ACC SQL_TEXT
--------------------- ------------------------------ ------------- --- --- --------------------------------------------------------------------------------
SQL_75b06ae056223f5f  SQL_PLAN_7bc3aw1b24guzb860bcf2  MANUAL-LOAD  YES YES select /*+ no_index(t2 idx_t2) */object_name,object_id from t2 where object_id=4

2.3.改写SQL,使其强制走索引

SQL> select /*+ index(t2 idx_t2) */object_name,object_id from t2 where object_id=4;

OBJECT_NAME	OBJECT_ID
----------- ---------
TAB$		    4

SQL> explain plan for select /*+ index(t2 idx_t2) */object_name,object_id from t2 where object_id=4;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------
Plan hash value: 2008370210

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

Predicate Information (identified by operation id):

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------

   2 - access("OBJECT_ID"=4)

2.4.使用改写SQL所对应的sql_id和Plan hash value,以及原SQL Plan Baseline的sql_handle值,生成新的SQL Plan Baseline

SQL> select SQL_ID from v$sqlarea where sql_text='select /*+index(t2 idx_t2) */object_name,object_id from t2 where object_id=4';

SQL_ID
-------------
96ct19dgmkpax

SQL> exec :temp :=dbms_spm.load_plans_from_cursor_cache(sql_id =>'96ct19dgmkpax',plan_hash_value => 2008370210,sql_handle => 'SQL_75b06ae056223f5f');

PL/SQL procedure successfully completed.

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

SQL_HANDLE		       PLAN_NAME		                ORIGIN	    ENA ACC SQL_TEXT
--------------------- ------------------------------ -------------- --- --- --------------------------------------------------------------------------------
SQL_75b06ae056223f5f  SQL_PLAN_7bc3aw1b24guz24c6dbb6  MANUAL-LOAD   YES YES select /*+ no_index(t2 idx_t2) */object_name,object_id from t2 where object_id=4
SQL_75b06ae056223f5f  SQL_PLAN_7bc3aw1b24guzb860bcf2  MANUAL-LOAD   YES YES select /*+ no_index(t2 idx_t2) */object_name,object_id from t2 where object_id=4

2.5.删除原有的执行计划,也就是全表扫描所对应的SQL Plan Baseline

SQL> exec :temp :=dbms_spm.drop_sql_plan_baseline(sql_handle =>'SQL_75b06ae056223f5f',plan_name => 'SQL_PLAN_7bc3aw1b24guzb860bcf2');

PL/SQL procedure successfully completed.

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

SQL_HANDLE		       PLAN_NAME		                 ORIGIN	    ENA ACC SQL_TEXT
--------------------- ------------------------------ -------------- --- --- --------------------------------------------------------------------------------
SQL_75b06ae056223f5f  SQL_PLAN_7bc3aw1b24guz24c6dbb6   MANUAL-LOAD  YES YES select /*+ no_index(t2 idx_t2) */object_name,object_id from t2 where object_id=4

2.6.执行查询,并查看执行计划

SQL> explain plan for select /*+ no_index(t2 idx_t2) */object_name,object_id from t2 where object_id=4;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------
Plan hash value: 2008370210

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

Predicate Information (identified by operation id):

PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------

   2 - access("OBJECT_ID"=4)

Note
-----
   - SQL plan baseline "SQL_PLAN_7bc3aw1b24guz24c6dbb6" used for this statement

此时手工生成SQL plan baseline就完成了在不改变目标SQL的情况下,更改其执行计划的目的。


参考:崔华 著 《基于Oracle的SQL优化》
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐