Oracle固定执行计划的方法----SQL Profie(二)
继续学习SQL Profile的手工创建方法,SQL Profile的基本知识参考文章《Oracle固定执行计划的方法----SQL Profie(一)》
一、手工创建 SQL Profile
某些场景下我们需要手工创建 SQL Profile,例如:
- 稳定一个已知最优执行计划的 SQL。
- 在不修改应用 SQL 的情况下强制改变执行计划。
手工构建sql profile 的hint(提示)的两种方法
1、自己手工编写提示,写入sys.sqlprof_attr数据类型,再通过DBMS_SQLTUNE.IMPORT_SQL_PROFILE,写入profile配置文件中
2、更常用的方法,从 Oracle 10g 开始,V$SQL_PLAN 中包含了 SQL 语句的 OUTLINE Data(大纲数据),这些数据包含了稳定执行计划所需要的所有 Hints,基于此,对于需要通过SQL Profile来稳定执行计划的SQL语句,可以通过添加hint提示后执行语句,不断进行尝试,直到该SQL语句的执行计划达到我们的预期,然后根据预期的执行计划,获取OUTLINE Data(大纲数据),最终通过IMPORT_SQL_PROFILE过程,写入profile配置文件中。
使用 IGNORE_OPTIM_EMBEDDED_HINTS 覆盖原始 Hints
如果原始 SQL 中已经包含了 Hints,而你想让 SQL Profile 覆盖这些 Hints,可以在 SQL Profile 中保留 IGNORE_OPTIM_EMBEDDED_HINTS 提示。这样 SQL Profile 中的 Hints 将优先于 SQL 文本中的 Hints。
DBMS_SQLTUNE.IMPORT_SQL_PROFILE 过程
手工创建 SQL Profile 的核心工具是 DBMS_SQLTUNE.IMPORT_SQL_PROFILE 过程。
sql
DESC dbms_sqltune
PROCEDURE IMPORT_SQL_PROFILE
Argument Name Type In/Out Default?
------------------------------ ----------------------- ------ --------
SQL_TEXT CLOB IN
PROFILE SQLPROF_ATTR IN
NAME VARCHAR2 IN DEFAULT
DESCRIPTION VARCHAR2 IN DEFAULT
CATEGORY VARCHAR2 IN DEFAULT
VALIDATE BOOLEAN IN DEFAULT
REPLACE BOOLEAN IN DEFAULT
FORCE_MATCH BOOLEAN IN DEFAULT
参数列表:
| 参数名 | 类型 | 输入/输出 | 默认值? | 说明 |
|---|---|---|---|---|
| SQL_TEXT | CLOB | IN | - | 需要绑定 SQL Profile 的 SQL 语句文本 |
| PROFILE | SQLPROF_ATTR | IN | - | Hints 集合 |
| NAME | VARCHAR2 | IN | DEFAULT | SQL Profile 名称 |
| DESCRIPTION | VARCHAR2 | IN | DEFAULT | 描述信息 |
| CATEGORY | VARCHAR2 | IN | DEFAULT | 分类(用于控制哪些会话可使用) |
| VALIDATE | BOOLEAN | IN | DEFAULT | 是否验证 Hints 有效性 |
| REPLACE | BOOLEAN | IN | DEFAULT | 是否替换已存在的同名 SQL Profile |
| FORCE_MATCH | BOOLEAN | IN | DEFAULT | 是否强制匹配(忽略空格、大小写差异) |
其中 PROFILE 参数的类型为 SYS.SQLPROF_ATTR,这是一个 VARCHAR2 的集合类型:
sql
SELECT text FROM dba_source WHERE name = 'SQLPROF_ATTR' AND owner = 'SYS';
输出:
TYPE sqlprof_attr AS VARRAY(2000) OF VARCHAR2(500)
这意味着每个 SQL Profile 最多可以包含 2000 个 Hints ,每个 Hint 的长度不超过 500 个字符。
二、案例(从 Outline Data 创建 SQL Profile)
Oracle数据库版本:11.2.0.4
数据库测试用户: USER1
1、准备工作
sql
-- 创建 T1 表:取 dba_objects 的前 50000 行
CREATE TABLE t1 AS
SELECT object_id, object_name
FROM dba_objects
WHERE rownum <= 50000;
-- 创建 T2 表:复制 dba_objects 的全部数据
CREATE TABLE t2 AS
SELECT * FROM dba_objects;
select count(*) from t1;
COUNT(*)
----------
50000
select count(*) from t2;
COUNT(*)
----------
86315
-- 在 T2 表的 object_id 上创建索引
CREATE INDEX ind_t2 ON t2(object_id);
-- 收集 T1和T2 表统计信息
BEGIN
DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'user1',
TABNAME=>'T1',
ESTIMATE_PERCENT=>1,
METHOD_OPT=>'FOR ALL COLUMNS SIZE 1',
NO_INVALIDATE=>FALSE,
CASCADE=>TRUE,
DEGREE => 4);
END;
/
BEGIN
DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'user1',
TABNAME=>'T2',
ESTIMATE_PERCENT=>1,
METHOD_OPT=>'FOR ALL COLUMNS SIZE 1',
NO_INVALIDATE=>FALSE,
CASCADE=>TRUE,
DEGREE => 4);
END;
/
2、执行目标 SQL 并观察执行计划
sql
set autotrace on
SELECT t1.*, t2.owner
FROM t1, t2
WHERE t1.object_name LIKE '%T2%'
AND t1.object_id = t2.object_id;
set autotrace off
Execution Plan
----------------------------------------------------------
Plan hash value: 1838229974
-- 执行计划,使用哈希连接
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2512 | 100K| 414 (1)| 00:00:05 |
|* 1 | HASH JOIN | | 2512 | 100K| 414 (1)| 00:00:05 |
|* 2 | TABLE ACCESS FULL| T1 | 2512 | 75360 | 70 (0)| 00:00:01 |
| 3 | TABLE ACCESS FULL| T2 | 87356 | 938K| 344 (1)| 00:00:05 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
2 - filter("T1"."OBJECT_NAME" LIKE '%T2%' AND "T1"."OBJECT_NAME" IS NOT NULL)
Statistics
----------------------------------------------------------
5 recursive calls
0 db block gets
1485 consistent gets
0 physical reads
0 redo size
2923 bytes sent via SQL*Net to client
552 bytes received via SQL*Net from client
5 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
58 rows processed
**执行计划结果:**两张表都是全表扫描(FULL TABLE SCAN)+ 哈希连接(HASH JOIN)
3、给SQL添加hint提示,使得语句的执行计划达到预期
语句添加提示后(/*+ sql_profile_test2 USE_NL(t1 t2) INDEX(t2) */ ),再次执行
set autotrace on
SELECT /*+ sql_profile_test2 USE_NL(t1 t2) INDEX(t2) */ t1.*, t2.owner
FROM t1, t2
WHERE t1.object_name LIKE '%T1%'
AND t1.object_id = t2.object_id;
set autotrace off
Execution Plan
----------------------------------------------------------
Plan hash value: 1022743391
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2512 | 100K| 5095 (1)| 00:01:02 |
| 1 | NESTED LOOPS | | 2512 | 100K| 5095 (1)| 00:01:02 |
| 2 | NESTED LOOPS | | 2512 | 100K| 5095 (1)| 00:01:02 |
|* 3 | TABLE ACCESS FULL | T1 | 2512 | 75360 | 70 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | T2_IDX | 1 | | 1 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 11 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter("T1"."OBJECT_NAME" LIKE '%T1%' AND "T1"."OBJECT_NAME" IS NOT NULL)
4 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
308 consistent gets
0 physical reads
0 redo size
2165 bytes sent via SQL*Net to client
541 bytes received via SQL*Net from client
4 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
38 rows processed
结果:添加提示后,确实获得了我们想要的执行计划(嵌套循环连接和T2表索引)
4、获取 Outline Data提示集合
SQL语句在我们添加提示后,达到我们想要的执行计划语句的。
通过DBMS_XPLAN.DISPLAY_CURSOR('','','outline')方法,获取outline提示集合,如下
1)获取SQL语句在内存中的信息
set linesize 200 pagesize 999
col sql_text format a40
SELECT sql_text, sql_id, hash_value, child_number
FROM v$sql
WHERE sql_text LIKE '%sql_profile_test2%'
AND sql_text NOT LIKE '%v$sql%';
SQL_TEXT SQL_ID HASH_VALUE CHILD_NUMBER
---------------------------------------- ------------- ---------- ------------
SELECT /*+ sql_profile_test2 USE_NL(t1 t 2qputnnd0wc1m 437137459 0
2) INDEX(t2) */ t1.*, t2.owner FROM t1,
t2 WHERE t1.object_name LIKE '%T1%' AN
D t1.object_id = t2.object_id
2)获取SQL语句在内存中的实际执行计划,带outline提示信息的输出!
set linesize 200 pagesize 999
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('2qputnnd0wc1m','','outline'));
Plan hash value: 1022743391
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 5095 (100)| |
| 1 | NESTED LOOPS | | 2512 | 100K| 5095 (1)| 00:01:02 |
| 2 | NESTED LOOPS | | 2512 | 100K| 5095 (1)| 00:01:02 |
|* 3 | TABLE ACCESS FULL | T1 | 2512 | 75360 | 70 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | T2_IDX | 1 | | 1 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 11 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Outline Data --下面这一部分就是outline提示集合!!
-------------
/*+
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" "T1"@"SEL$1")
INDEX(@"SEL$1" "T2"@"SEL$1" ("T2"."OBJECT_ID"))
LEADING(@"SEL$1" "T1"@"SEL$1" "T2"@"SEL$1")
USE_NL(@"SEL$1" "T2"@"SEL$1")
NLJ_BATCHING(@"SEL$1" "T2"@"SEL$1")
END_OUTLINE_DATA
*/
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter(("T1"."OBJECT_NAME" LIKE '%T1%' AND "T1"."OBJECT_NAME" IS NOT NULL))
4 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
5、编写 PL/SQL 脚本导入SQL Profile
通过上面获取的outline提示集合来编写和构建
DECLARE
sql_txt CLOB;
v_hint SYS.SQLPROF_ATTR;
BEGIN
-- 1. 定义 SQL 文本 (尽量完全匹配,force_match=>true匹配模式变量值可以不一样)
sql_txt := q'[
select t1.*, t2.owner
from t1, t2
where t1.object_name like '%T1%'
and t1.object_id = t2.object_id
]';
-- 2. 定义 Hints (从 Outline Data 中提取)
v_hint := SYS.SQLPROF_ATTR(
'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" "T1"@"SEL$1")',
'INDEX(@"SEL$1" "T2"@"SEL$1" ("T2"."OBJECT_ID"))',
'LEADING(@"SEL$1" "T1"@"SEL$1" "T2"@"SEL$1")',
'USE_NL(@"SEL$1" "T2"@"SEL$1")',
'NLJ_BATCHING(@"SEL$1" "T2"@"SEL$1")',
'END_OUTLINE_DATA'
);
-- 3. 导入 Profile
DBMS_SQLTUNE.IMPORT_SQL_PROFILE (
sql_text => sql_txt,
profile => v_hint,
name => 'MANUAL_PROFILE_T1_T2', -- 自定义名称
category => 'DEFAULT',
description => '手工锁定 NL 连接',
force_match => TRUE -- 关键:TRUE 表示忽略字面量差异
);
DBMS_OUTPUT.PUT_LINE('Profile 创建成功');
END;
/
6、验证SQL Profile是否创建
SELECT name, category, signature, type, status, force_matching
FROM dba_sql_profiles;
NAME CATEGORY SIGNATURE TYPE STATUS FOR
------------------------------ ------------------------------ ---------- ------- -------- ---
MANUAL_PROFILE_T1_T2 DEFAULT 3.9607E+18 MANUAL ENABLED YES
7、验证 SQL Profile 中的 Hints 是否被正确存储
sql
SELECT COMP_DATA
FROM dba_sql_profiles a, sys.sqlobj$data b
WHERE a.signature = b.signature
AND a.name = 'MANUAL_PROFILE_T1_T2';
输出:
COMP_DATA
--------------------------------------------------------------------------------
<outline_data><hint><![CDATA[BEGIN_OUTLINE_DATA]]></hint><hint><![CDATA[IGNORE_O
PTIM_EMBEDDED_HINTS]]></hint><hint><![CDATA[OPTIMIZER_FEATURES_ENABLE('11.2.0.4'
)]]></hint><hint><![CDATA[DB_VERSION('11.2.0.4')]]></hint><hint><![CDATA[ALL_ROW
S]]></hint><hint><![CDATA[OUTLINE_LEAF(@"SEL$1")]]></hint><hint><![CDATA[FULL(@"
SEL$1" "T1"@"SEL$1")]]></hint><hint><![CDATA[INDEX(@"SEL$1" "T2"@"SEL$1" ("T2"."
OBJECT_ID"))]]></hint><hint><![CDATA[LEADING(@"SEL$1" "T1"@"SEL$1" "T2"@"SEL$1")
]]></hint><hint><![CDATA[USE_NL(@"SEL$1" "T2"@"SEL$1")]]></hint><hint><![CDATA[N
LJ_BATCHING(@"SEL$1" "T2"@"SEL$1")]]></hint><hint><![CDATA[END_OUTLINE_DATA]]></
hint></outline_data>
8、验证SQL Profile是否生效
执行对应的 SQL:
sql
set autotrace on
SELECT t1.*, t2.owner
FROM t1, t2
WHERE t1.object_name LIKE '%T2%'
AND t1.object_id = t2.object_id;
set autotrace off
查看执行计划:
Execution Plan
----------------------------------------------------------
Plan hash value: 1386590592
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2512 | 100K| 5095 (1)| 00:01:02 |
| 1 | NESTED LOOPS | | 2512 | 100K| 5095 (1)| 00:01:02 |
| 2 | NESTED LOOPS | | 2512 | 100K| 5095 (1)| 00:01:02 |
|* 3 | TABLE ACCESS FULL | T1 | 2512 | 75360 | 70 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IND_T2 | 1 | | 1 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 11 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter("T1"."OBJECT_NAME" LIKE '%T2%' AND "T1"."OBJECT_NAME" IS NOTNULL)
4 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
Note
-----
- SQL profile "MANUAL_PROFILE_T1_T2" used for this statement
Statistics
----------------------------------------------------------
7 recursive calls
0 db block gets
318 consistent gets
1 physical reads
0 redo size
2923 bytes sent via SQL*Net to client
552 bytes received via SQL*Net from client
5 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
58 rows processed
可以看到 Note 显示 SQL Profile 被使用了,执行计划变为是嵌套循环连接 + 索引范围扫描------达到预期效果。
9、验证 SQL Profile 的稳定性
现在,我们大幅度修改 T1 表的统计信息行数,模拟统计信息严重失真的场景:
sql
EXEC dbms_stats.set_table_stats('USER1', 'T1', numrows => 50000000);
再次执行 SQL:
sql
set autotrace on
SELECT t1.*, t2.owner
FROM t1, t2
WHERE t1.object_name LIKE '%T2%'
AND t1.object_id = t2.object_id;
set autotrace off
执行计划:
Execution Plan
----------------------------------------------------------
set autotrace off
Plan hash value: 1386590592
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2500K| 97M| 5001K (1)| 16:40:18 |
| 1 | NESTED LOOPS | | 2500K| 97M| 5001K (1)| 16:40:18 |
| 2 | NESTED LOOPS | | 2500K| 97M| 5001K (1)| 16:40:18 |
|* 3 | TABLE ACCESS FULL | T1 | 2500K| 71M| 437 (84)| 00:00:06 |
|* 4 | INDEX RANGE SCAN | IND_T2 | 1 | | 1 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 11 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter("T1"."OBJECT_NAME" LIKE '%T2%' AND "T1"."OBJECT_NAME" IS NOT NULL)
4 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
Note
-----
- SQL profile "MANUAL_PROFILE_T1_T2" used for this statement
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
313 consistent gets
0 physical reads
0 redo size
2923 bytes sent via SQL*Net to client
552 bytes received via SQL*Net from client
5 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
58 rows processed
观察结果:
- 优化器虽然评估 T1 表返回 2500K 行(因为统计信息显示 T1 有 5000 万行),但执行计划仍然保持不变------仍然是嵌套循环连接 + 索引范围扫描。
这说明:SQL Profile 成功稳定了执行计划 ,不受统计信息剧烈变化的影响。但需要注意的是,这里的稳定是通过强制指定 Hints(USE_NL 和 INDEX)实现的,而非通过 OPT_ESTIMATE 缩放系数。
三、使用 COE_XFR_SQL_PROFILE 脚本构建SQL Profile------自动化利器
从 MOS(My Oracle Support)可以下载到 SQLT (SQLTXPLAIN) 工具包。其中包含的 coe_xfr_sql_profile.sql 脚本可以自动从 shared pool 或 AWR 中提取指定 SQL ID 的 Outline Data 并生成创建 SQL Profile 的脚本。
主要作用:如果一个SQL语句包含多条执行计划,且经常出现执行速度变化的情况,可以通过该脚本,选择一个预期的执行计划(当前内存中、AWR历史资料库中保存的执行计划),生成SQL Profile,来稳定该语句的执行计划!
6.1 下载与准备
- 从 MOS 下载
sqlt.zip文件(文档 ID: 215187.1)。 - 解压后,在 目录中找到
coe_xfr_sql_profile.sql脚本。
6.2 执行脚本
首先在 shared pool 中执行目标 SQL:
sql
SELECT /*+ sql_profile_test3 */ t1.*, t2.owner
FROM t1, t2
WHERE t1.object_name LIKE '%T1%'
AND t1.object_id = t2.object_id;
--注意需要查询 plan_hash_value 这一列
set linesize 200 pagesize 999
col sql_text format a40
SELECT sql_text, sql_id, hash_value, child_number,plan_hash_value
FROM v$sql
WHERE sql_text LIKE '%sql_profile_test3%'
AND sql_text NOT LIKE '%v$sql%';
SQL_TEXT SQL_ID HASH_VALUE CHILD_NUMBER PLAN_HASH_VALUE
---------------------------------------- ------------- ---------- ------------ ---------------
SELECT /*+ sql_profile_test3 */ t1.*, t2 cdy6d6t5quzfd 1265466829 0 2959412835
.owner FROM t1, t2 WHERE t1.object_name
LIKE '%T1%' AND t1.object_id = t2.obje
ct_id
执行 coe_xfr_sql_profile.sql 脚本:
sql
@coe_xfr_sql_profile.sql
SQL> @coe_xfr_sql_profile.sql
Parameter 1:
SQL_ID (required)
Enter value for 1: cdy6d6t5quzfd ---输入sql_id
PLAN_HASH_VALUE AVG_ET_SECS
--------------- -----------
2959412835 .033
Parameter 2:
PLAN_HASH_VALUE (required)
Enter value for 2: 2959412835 --输入语句想要生成sql profile的plan_hash_value
Values passed to coe_xfr_sql_profile:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SQL_ID : "cdy6d6t5quzfd"
PLAN_HASH_VALUE: "2959412835"
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_cdy6d6t5quzfd_2959412835.sql --生成一个脚本文件,运行该脚本,就可以创建sql profile
on TARGET system in order to create a custom SQL Profile
with plan 2959412835 linked to adjusted sql_text.
COE_XFR_SQL_PROFILE completed.
脚本会生成另一个脚本 coe_xfr_sql_profile_cdy6d6t5quzfd_2959412835.sql。
6.3 执行生成的脚本
运行生成的脚本即可创建 SQL Profile:
sql
@coe_xfr_sql_profile_cdy6d6t5quzfd_2959412835.sql
SQL>@coe_xfr_sql_profile_cdy6d6t5quzfd_2959412835.sql
SQL>REM
SQL>REM $Header: 215187.1 coe_xfr_sql_profile_cdy6d6t5quzfd_2959412835.sql 11.4.4.4 2026/08/15 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_cdy6d6t5quzfd_2959412835.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 cdy6d6t5quzfd based on plan hash
SQL>REM value 2959412835.
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_cdy6d6t5quzfd_2959412835.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_cdy6d6t5quzfd_2959412835');
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 /*+ sql_profile_test3 */ t1.*, t2.owner
15 FROM t1, t2
16 WHERE]');
17 wa(q'[ t1.object_name LIKE '%T1%'
18 AND t1.object_id = t2.object_id]');
19 DBMS_LOB.CLOSE(sql_txt);
20 h := SYS.SQLPROF_ATTR(
21 q'[BEGIN_OUTLINE_DATA]',
22 q'[IGNORE_OPTIM_EMBEDDED_HINTS]',
23 q'[OPTIMIZER_FEATURES_ENABLE('11.2.0.4')]',
24 q'[DB_VERSION('11.2.0.4')]',
25 q'[ALL_ROWS]',
26 q'[OUTLINE_LEAF(@"SEL$1")]',
27 q'[FULL(@"SEL$1" "T2"@"SEL$1")]',
28 q'[FULL(@"SEL$1" "T1"@"SEL$1")]',
29 q'[LEADING(@"SEL$1" "T2"@"SEL$1" "T1"@"SEL$1")]',
30 q'[USE_HASH(@"SEL$1" "T1"@"SEL$1")]',
31 q'[END_OUTLINE_DATA]');
32 :signature := DBMS_SQLTUNE.SQLTEXT_TO_SIGNATURE(sql_txt);
33 :signaturef := DBMS_SQLTUNE.SQLTEXT_TO_SIGNATURE(sql_txt, TRUE);
34 DBMS_SQLTUNE.IMPORT_SQL_PROFILE (
35 sql_text => sql_txt,
36 profile => h,
37 name => 'coe_cdy6d6t5quzfd_2959412835',
38 description => 'coe cdy6d6t5quzfd 2959412835 '||:signature||' '||:signaturef||'',
39 category => 'DEFAULT',
40 validate => TRUE,
41 replace => TRUE,
42 force_match => FALSE /* TRUE:FORCE (match even when different literals in SQL). FALSE:EXACT (similar to CURSOR_SHARING) */ );
43 DBMS_LOB.FREETEMPORARY(sql_txt);
44 END;
45 /
PL/SQL procedure successfully completed.
SQL>WHENEVER SQLERROR CONTINUE
SQL>SET ECHO OFF;
SIGNATURE
---------------------
10081132228680031145
SIGNATUREF
---------------------
7070267937381023371
... manual custom SQL Profile has been created
COE_XFR_SQL_PROFILE_cdy6d6t5quzfd_2959412835 completed
验证 SQL Profile 中的 Hints:
sql
SELECT name, category, signature, type, status, force_matching
FROM dba_sql_profiles;
NAME CATEGORY SIGNATURE TYPE STATUS FOR
------------------------------ -------------- ---------- ------- -------- ---
MANUAL_PROFILE_T1_T2 DEFAULT 3.9607E+18 MANUAL ENABLED YES
coe_cdy6d6t5quzfd_2959412835 DEFAULT 1.0081E+19 MANUAL ENABLED NO
set linesize 200 pagesize 9999
set long 100000
SELECT COMP_DATA
FROM dba_sql_profiles a, sys.sqlobj$data b
WHERE a.signature = b.signature
AND a.name = 'coe_cdy6d6t5quzfd_2959412835';
COMP_DATA
--------------------------------------------------------------------------------
<outline_data><hint><![CDATA[BEGIN_OUTLINE_DATA]]></hint><hint><![CDATA[IGNORE_O
PTIM_EMBEDDED_HINTS]]></hint><hint><![CDATA[OPTIMIZER_FEATURES_ENABLE('11.2.0.4'
)]]></hint><hint><![CDATA[DB_VERSION('11.2.0.4')]]></hint><hint><![CDATA[ALL_ROW
S]]></hint><hint><![CDATA[OUTLINE_LEAF(@"SEL$1")]]></hint><hint><![CDATA[FULL(@"
SEL$1" "T2"@"SEL$1")]]></hint><hint><![CDATA[FULL(@"SEL$1" "T1"@"SEL$1")]]></hin
t><hint><![CDATA[LEADING(@"SEL$1" "T2"@"SEL$1" "T1"@"SEL$1")]]></hint><hint><![C
DATA[USE_HASH(@"SEL$1" "T1"@"SEL$1")]]></hint><hint><![CDATA[END_OUTLINE_DATA]]>
</hint></outline_data>
6.4 脚本的注意事项
- 脚本会从 shared pool 和 AWR 中获取 SQL 执行的各个执行计划的统计信息。
- 需要输入你认为正确的、需要稳定的执行计划的 hash value。
- 生成的 SQL Profile 名称格式为:
coe+sql_id+plan_hash_value。 force_match默认为 FALSE,可以手工修改生成的脚本将其改为 TRUE。- 也可以按需要修改生成脚本的其他内容。
七、高级技巧:在不修改 SQL 的情况下改变执行计划
在实际生产环境中,常常遇到这样的情况:SQL 语句的执行计划有问题,或者 SQL 使用了错误的 Hints(例如 /*+ RULE */),但应用代码不能修改或短期内无法修改。
有三种解决办法:
- 调整统计信息------不推荐,因为复杂、不稳定(统计信息可能被重新收集),且影响面广。
- 使用 Outline------方法复杂。
- 使用 SQL Profile------推荐方案。
7.1 核心思路:"乾坤大挪移"
使用 SQL Profile 改变 SQL 执行计划的核心思路是:
- 取得原始 SQL 的文本(如有可能还包括 SQL_ID)。
- 构造一个与原始 SQL 在逻辑上、结构上完全相同的 SQL。这里强制逻辑上和结构上相同,SQL 解析的用户名、SQL 中引用对象的用户名甚至是一些谓词条件都可以不同。
- 执行构造的 SQL,并取得构造的 SQL 的 Outline Data。
- 使用原始 SQL 的文本 和构造 SQL 的 Outline Data 创建 SQL Profile。
7.2 完整演示
第一步:原始 SQL(执行计划不优)
sql
SELECT /*+ old_sql full(t1) full(t2) use_hash(t1 t2) */ t1.*, t2.owner
FROM t1, t2
WHERE t1.object_name LIKE '%T1%'
AND t1.object_id = t2.object_id;
set linesize 200 pagesize 999
col sql_text format a40
SELECT sql_text, sql_id, hash_value, child_number,plan_hash_value
FROM v$sql
WHERE sql_text LIKE '%old_sql%'
AND sql_text NOT LIKE '%v$sql%';
SQL_TEXT SQL_ID HASH_VALUE CHILD_NUMBER PLAN_HASH_VALUE
---------------------------------------- ------------- ---------- ------------ ---------------
SELECT /*+ old_sql full(t1) full(t2) use g7ndz4jp2j3uh 1781043024 0 2959412835
_hash(t1 t2) */ t1.*, t2.owner FROM t1,
t2 WHERE t1.object_name LIKE '%T1%' AN
D t1.object_id = t2.object_id
原始 SQL 中使用了 FULL(t1)、FULL(t2) 和 USE_HASH(t1 t2) 这些 Hints,强制使用全表扫描 + 哈希连接。执行后 SQL_ID 为 g7ndz4jp2j3uh。
执行计划:
select * from table(dbms_xplan.display_cursor('g7ndz4jp2j3uh','',''));
Plan hash value: 2959412835
-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | | 5854 (100)| |
|* 1 | HASH JOIN | | 2500K| 97M| 1968K| 5854 (7)| 00:01:11 |
| 2 | TABLE ACCESS FULL| T2 | 87356 | 938K| | 344 (1)| 00:00:05 |
|* 3 | TABLE ACCESS FULL| T1 | 2500K| 71M| | 437 (84)| 00:00:06 |
-----------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
3 - filter(("T1"."OBJECT_NAME" LIKE '%T1%' AND "T1"."OBJECT_NAME" IS NOT NULL))
第二步:构造目标 SQL(我们希望的执行计划)
构造一条在逻辑上和结构上与原始 SQL 完全相同的 SQL,但使用我们希望的执行计划:
sql
SELECT /*+ new_sql index(t2) use_nl(t1 t2) */ t1.*, t2.owner
FROM t1, t2
WHERE t1.object_name LIKE '%T1%'
AND t1.object_id = t2.object_id;
set linesize 200 pagesize 999
col sql_text format a40
SELECT sql_text, sql_id, hash_value, child_number,plan_hash_value
FROM v$sql
WHERE sql_text LIKE '%new_sql%'
AND sql_text NOT LIKE '%v$sql%';
SQL_TEXT SQL_ID HASH_VALUE CHILD_NUMBER PLAN_HASH_VALUE
---------------------------------------- ------------- ---------- ------------ ---------------
SELECT /*+ new_sql index(t2) use_nl(t1 t 0sz1znbny6v0m 3923995667 0 1386590592
2) */ t1.*, t2.owner FROM t1, t2 WHERE t
1.object_name LIKE '%T1%' AND t1.objec
t_id = t2.object_id
执行计划:
select * from table(dbms_xplan.display_cursor('0sz1znbny6v0m','',''));
Plan hash value: 1386590592
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 5001K(100)| |
| 1 | NESTED LOOPS | | 2500K| 97M| 5001K (1)| 16:40:18 |
| 2 | NESTED LOOPS | | 2500K| 97M| 5001K (1)| 16:40:18 |
|* 3 | TABLE ACCESS FULL | T1 | 2500K| 71M| 437 (84)| 00:00:06 |
|* 4 | INDEX RANGE SCAN | IND_T2 | 1 | | 1 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 11 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter(("T1"."OBJECT_NAME" LIKE '%T1%' AND "T1"."OBJECT_NAME" IS NOT NULL))
4 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
第三步:提取构造 SQL 的 Outline Data
使用 coe_xfr_sql_profile.sql 脚本提取构造 SQL 的 Outline Data:
sql
@coe_xfr_sql_profile.sql
-- 输入构造 SQL 的 SQL_ID(0sz1znbny6v0m)和 PLAN_HASH_VALUE(1386590592)
-- /*+ new_sql index(t2) use_nl(t1 t2) */SQL 的 Outline Data
SQL>@coe_xfr_sql_profile.sql
Parameter 1:
SQL_ID (required)
Enter value for 1: 0sz1znbny6v0m
PLAN_HASH_VALUE AVG_ET_SECS
--------------- -----------
1386590592 .033
Parameter 2:
PLAN_HASH_VALUE (required)
Enter value for 2: 1386590592
Values passed to coe_xfr_sql_profile:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SQL_ID : "0sz1znbny6v0m"
PLAN_HASH_VALUE: "1386590592"
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_0sz1znbny6v0m_1386590592.sql
on TARGET system in order to create a custom SQL Profile
with plan 1386590592 linked to adjusted sql_text.
COE_XFR_SQL_PROFILE completed.
生成的脚本 coe_xfr_sql_profile_0sz1znbny6v0m_1386590592.sql 中包含 Outline Data:
sql
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'[FULL(@"SEL$1" "T1"@"SEL$1")]',
q'[INDEX(@"SEL$1" "T2"@"SEL$1" ("T2"."OBJECT_ID"))]',
q'[LEADING(@"SEL$1" "T1"@"SEL$1" "T2"@"SEL$1")]',
q'[USE_NL(@"SEL$1" "T2"@"SEL$1")]',
q'[NLJ_BATCHING(@"SEL$1" "T2"@"SEL$1")]',
q'[END_OUTLINE_DATA]');
第四步:为原始 SQL 创建 SQL Profile
针对原始 SQL(SQL_ID = g7ndz4jp2j3uh,PLAN_HASH_VALUE = 2959412835)使用 coe_xfr_sql_profile.sql 生成脚本。
然后手工修改 生成的脚本 coe_xfr_sql_profile_g7ndz4jp2j3uh_2959412835.sql:
-
将
h := SYS.SQLPROF_ATTR(...)那段替换成上一步得到的 Outline Data。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'[FULL(@"SEL$1" "T2"@"SEL$1")]', q'[FULL(@"SEL$1" "T1"@"SEL$1")]', q'[LEADING(@"SEL$1" "T2"@"SEL$1" "T1"@"SEL$1")]', q'[USE_HASH(@"SEL$1" "T1"@"SEL$1")]', q'[END_OUTLINE_DATA]'); 改为 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'[FULL(@"SEL$1" "T1"@"SEL$1")]', q'[INDEX(@"SEL$1" "T2"@"SEL$1" ("T2"."OBJECT_ID"))]', q'[LEADING(@"SEL$1" "T1"@"SEL$1" "T2"@"SEL$1")]', q'[USE_NL(@"SEL$1" "T2"@"SEL$1")]', q'[NLJ_BATCHING(@"SEL$1" "T2"@"SEL$1")]', q'[END_OUTLINE_DATA]'); -
将
force_match从FALSE改为TRUE。
第五步:执行修改后的脚本
sql
@@coe_xfr_sql_profile_g7ndz4jp2j3uh_2959412835.sql
PL/SQL 过程已成功完成。
SIGNATURE
---------------------
15211772502623681692
SIGNATUREF
---------------------
1099879712749910124
... manual custom SQL Profile has been created
COE_XFR_SQL_PROFILE_g7ndz4jp2j3uh_2959412835 completed
第六步:验证效果
执行原始 SQL(注意这里将 LIKE 条件从 %T1% 改为 %T2%,以验证 force_match 是否起作用):
sql
SELECT /*+ old_sql full(t1) full(t2) use_hash(t1 t2) */ t1.*, t2.owner
FROM t1, t2
WHERE t1.object_name LIKE '%T1%'
AND t1.object_id = t2.object_id;
set linesize 200 pagesize 999
col sql_text format a40
SELECT sql_text, sql_id, hash_value, child_number,plan_hash_value
2 FROM v$sql
3 WHERE sql_text LIKE '%old_sql%'
4 AND sql_text NOT LIKE '%v$sql%';
SQL_TEXT SQL_ID HASH_VALUE CHILD_NUMBER PLAN_HASH_VALUE
---------------------------------------- ------------- ---------- ------------ ---------------
SELECT /*+ old_sql full(t1) full(t2) use g7ndz4jp2j3uh 1781043024 0 1386590592
_hash(t1 t2) */ t1.*, t2.owner FROM t1,
t2 WHERE t1.object_name LIKE '%T1%' AN
D t1.object_id = t2.object_id
执行计划:
SQL>select * from table(dbms_xplan.display_cursor('g7ndz4jp2j3uh','',''));
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------
SQL_ID g7ndz4jp2j3uh, child number 0
-------------------------------------
SELECT /*+ old_sql full(t1) full(t2) use_hash(t1 t2) */ t1.*, t2.owner
FROM t1, t2 WHERE t1.object_name LIKE '%T1%' AND t1.object_id =
t2.object_id
Plan hash value: 1386590592
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 5001K(100)| |
| 1 | NESTED LOOPS | | 2500K| 97M| 5001K (1)| 16:40:18 |
| 2 | NESTED LOOPS | | 2500K| 97M| 5001K (1)| 16:40:18 |
|* 3 | TABLE ACCESS FULL | T1 | 2500K| 71M| 437 (84)| 00:00:06 |
|* 4 | INDEX RANGE SCAN | IND_T2 | 1 | | 1 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| T2 | 1 | 11 | 2 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - filter(("T1"."OBJECT_NAME" LIKE '%T1%' AND "T1"."OBJECT_NAME" IS NOT NULL))
4 - access("T1"."OBJECT_ID"="T2"."OBJECT_ID")
Note
-----
- SQL profile coe_g7ndz4jp2j3uh_2959412835 used for this statement
成功! SQL Profile 起作用了,执行计划从全表扫描 + 哈希连接变成了嵌套循环连接 + 索引范围扫描。
7.3 自动化改进
如果觉得手动修改脚本比较繁琐,可以修改 create_sql_profile.sql 脚本。将其中提取 Outline Data 的部分:
sql
SELECT extractvalue(value(d), '/hint') AS outline_hints BULK COLLECT
INTO ar_profile_hints
FROM xmltable('/*/outline_data/hint' PASSING (
SELECT xmltype(other_xml) AS xmlval
FROM v$sql_plan
WHERE sql_id = '&&sql_id'
AND child_number = &&child_no
AND other_xml IS NOT NULL
)) d;
改为:
sql
SELECT extractvalue(value(d), '/hint') AS outline_hints BULK COLLECT
INTO ar_profile_hints
FROM xmltable('/*/outline_data/hint' PASSING (
SELECT xmltype(other_xml) AS xmlval
FROM v$sql_plan
WHERE sql_id = '&&modi_sql_id' -- 改为构造 SQL 的 SQL_ID
AND child_number = &&modi_child_no -- 改为构造 SQL 的 child_number
AND other_xml IS NOT NULL
)) d;
这样脚本就会自动从构造 SQL 的执行计划中提取 Outline Data,用于创建原始 SQL 的 Profile。