前面文章 oracle 为sql寻找更好的执行计划并绑定 介绍的方法只能在sql本身有过好执行计划的时候将其固定,如果sql从未有过好的执行计划,需要添加hint但又无法修改sql代码时,之前的方法就不合适,需要使用新的方法。
一、 问题背景
遇到过的一个案例就十分符合这种情况。11.2的数据库A中发现一个慢sql,执行超过4万秒。查询program得知是开发用的datagrip工具自动执行的语句,无法关闭也无法修改该sql,实际对业务功能没一点作用,对DB压力还特别大。
select O.owner as synonym_schema_name,
O.object_id as synonym_id,
S.synonym_name,
O.created as created_timestamp,
O.last_ddl_time as modified_timestamp,
S.db_link as origin_db_link,
S.table_owner as origin_schema_name,
S.table_name as origin_object_name
from sys.all_objects O,
sys.all_synonyms S
where O.object_type = 'SYNONYM'
and O.owner in ('XXX','XXXX')
and O.object_name not like '%/%'
-- and O.last_ddl_time >= :since
and S.owner = O.owner
and S.synonym_name = O.object_name
order by object_id;
之所以这么慢是因为all_synonyms 视图有bug,有时会导致sql执行特别慢,把all_synonyms改成dba_synonyms视图立刻就能出结果。
select O.owner as synonym_schema_name,
O.object_id as synonym_id,
S.synonym_name,
O.created as created_timestamp,
O.last_ddl_time as modified_timestamp,
S.db_link as origin_db_link,
S.table_owner as origin_schema_name,
S.table_name as origin_object_name
from sys.all_objects O,
sys.dba_synonyms S
where O.object_type = 'SYNONYM'
and O.owner in ('XXX','XXXX')
and O.object_name not like '%/%'
-- and O.last_ddl_time >= :since
and S.owner = O.owner
and S.synonym_name = O.object_name
order by object_id
问题是不能改代码,用户也没有查询dba_synonyms视图的权限,为这个语句去替换生产库all_synonyms的定义风险也比较大。尝试了mos中几个收集统计信息的方法还是不行,后来想到尝试给它添加/*+ rule */ hint。对于系统视图,如果有查询非常慢的情况可以试试加/*+ rule */ hint,有时效果奇佳,下面这个sql也是秒出结果。
select /*+ rule */ O.owner as synonym_schema_name,
O.object_id as synonym_id,
S.synonym_name,
O.created as created_timestamp,
O.last_ddl_time as modified_timestamp,
S.db_link as origin_db_link,
S.table_owner as origin_schema_name,
S.table_name as origin_object_name
from sys.all_objects O,
sys.all_synonyms S
where O.object_type = 'SYNONYM'
and O.owner in ('XXX','XXXX')
and O.object_name not like '%/%'
-- and O.last_ddl_time >= :since
and S.owner = O.owner
and S.synonym_name = O.object_name
order by object_id;
现在的问题变成了,如何把加hint这个sql的执行计划绑定到原sql
二、 解决方法
1. 使用coe_load_sql_profile.sql
不仅可以快速固定现有执行计划,还可用别的sql执行计划替换原sql执行计划。
- 执行计划必须在 cache 或 AWR 中
- 需用DBA权限用户执行(但不要用sys用户,否则会报错)
connect system/pass
SQL> @coe_load_sql_profile.sql
Parameter 1:
ORIGINAL_SQL_ID (required)
Enter value for 1: 329d885bxvrcr <-- 慢sql_id
Parameter 2:
MODIFIED_SQL_ID (required)
Enter value for 2: 4f74t4ab7rd5y <-- 快sql_id
PLAN_HASH_VALUE AVG_ET_SECS
-------------------- --------------------
2872589290 .003
Parameter 3:
PLAN_HASH_VALUE (required)
Enter value for 3: 2872589290 <-- 快执行计划的PLAN_HASH_VALUE
Values passed to coe_load_sql_profile:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ORIGINAL_SQL_ID: "329d885bxvrcr"
MODIFIED_SQL_ID: "4f74t4ab7rd5y"
PLAN_HASH_VALUE: "2872589290"
...
ORIGINAL:329D885BXVRCR MODIFIED:4F74T4AB7RD5Y PHV:2872589290 SIGNATURE:15822026218863957422 CREATED BY COE_LOAD_SQL_PROFILE.SQL
SQL>SET ECHO OFF;
****************************************************************************
* Enter password to export staging table STGTAB_SQLPROF_329d885bxvrcr
****************************************************************************
Export: Release 11.2.0.3.0 - Production on Sun Mar 11 14:45:47 2012
Copyright (c) 1982, 2011, Oracle and/or its affiliates. All rights reserved.
Password:
...
coe_load_sql_profile completed.
再次执行原sql
SQL> select ename from emp where ename='Name';
Plan hash value: 2872589290
---------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
---------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 6 | 3 (0)|
|* 1 | TABLE ACCESS FULL| EMP | 1 | 6 | 3 (0)|
---------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("ENAME"='Name')
Note
-----
- SQL profile "329D885BXVRCR_2872589290" used for this statement <-- 使用了SQL profile
2. 使用coe_load_sql_baseline.sql
同样不仅可以快速固定现有执行计划,还可用别的sql执行计划替换原sql执行计划。跟coe_load_sql_profile.sql 用法和输入参数是一样的,不过使用条件略有不同。
- 原sql必须在 cache 或 AWR 中
- 修改后的sql必须在 cache 中
- 需用DBA权限用户执行(但不要用sys用户,否则会报错)
SQL> @coe_load_sql_baseline.sql
Parameter 1:
ORIGINAL_SQL_ID (required)
Enter value for 1: 329d885bxvrcr <-- 慢sql_id
Parameter 2:
MODIFIED_SQL_ID (required)
Enter value for 2: 4f74t4ab7rd5y <-- 快sql_id
PLAN_HASH_VALUE AVG_ET_SECS
-------------------- --------------------
2872589290 .003
Parameter 3:
PLAN_HASH_VALUE (required)
Enter value for 3: 2872589290 <-- 快执行计划的PLAN_HASH_VALUE
Values passed to coe_load_sql_baseline:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ORIGINAL_SQL_ID: "329d885bxvrcr"
MODIFIED_SQL_ID: "4f74t4ab7rd5y"
PLAN_HASH_VALUE: " 2872589290"
...
****************************************************************************
* Enter <User_Name> password to export staging table STGTAB_BASELINE_329d885bxvrcr
****************************************************************************
Export: Release 11.2.0.3.0 - Production on Sun Mar 11 15:08:56 2012
Copyright (c) 1982, 2011, Oracle and/or its affiliates. All rights reserved.
Password:
...
deleting: coe_load_sql_baseline.log
coe_load_sql_baseline completed.
再次执行原sql
SQL> select ename from emp where ename='name';
Plan hash value: 2872589290
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 6 | 3 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| EMP | 1 | 6 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("ENAME"='name')
Note
-----
- SQL plan baseline "329D885BXVRCR_4F74T4AB7RD5Y" used for this statement <-- 使用了baseline
3. 使用SPM
比起前两种这个操作步骤复杂得多,建议使用前两种,整体思路是:
- 为原sql创建一个占位符baseline
- 禁用该baseline
- 在原sql handle下创建使用预期执行计划的新baseline
- 原sql将使用新的执行计划
- Create a placeholder baseline for the original query *
- Disable it
- Create a new baseline using the desired plan under the original sql handle
- The original will now use the new plan
*The creation of the original placeholder baseline is only necessary so that you have a handle to load the desired plan into
使用案例如下,原sql使用并行执行计划,我们的目标是不改变语句让它使用串行执行计划
- 执行原sql
SQL> variable ctgy varchar2(1);
SQL> exec :ctgy := 'K';
PL/SQL procedure successfully completed.
SQL> set autotrace on explain
SQL> SELECT /*+ parallel(s) */ prod_name, SUM(amount_sold)
FROM Sale s, Products p
WHERE s.prod_id=p.prod_id
AND prod_category = :ctgy
GROUP BY prod_name;
Execution Plan
----------------------------------------------------------
Plan hash value: 1377251112
---------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
---------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 145K| 6815K| 11892 (6)| 00:00:01 | | | |
| 1 | PX COORDINATOR | | | | | | | | |
| 2 | PX SEND QC (RANDOM) | :TQ10002 | 145K| 6815K| 11892 (6)| 00:00:01 | Q1,02 | P->S | QC (RAND) |
| 3 | HASH GROUP BY | | 145K| 6815K| 11892 (6)| 00:00:01 | Q1,02 | PCWP | |
| 4 | PX RECEIVE | | 145K| 6815K| 11892 (6)| 00:00:01 | Q1,02 | PCWP | |
| 5 | PX SEND HASH | :TQ10001 | 145K| 6815K| 11892 (6)| 00:00:01 | Q1,01 | P->P | HASH |
| 6 | HASH GROUP BY | | 145K| 6815K| 11892 (6)| 00:00:01 | Q1,01 | PCWP | |
|* 7 | HASH JOIN | | 145K| 6815K| 11790 (5)| 00:00:01 | Q1,01 | PCWP | |
| 8 | BUFFER SORT | | | | | | Q1,01 | PCWC | |
| 9 | PX RECEIVE | | 1 | 22 | 19 (0)| 00:00:01 | Q1,01 | PCWP | |
| 10 | PX SEND BROADCAST | :TQ10000 | 1 | 22 | 19 (0)| 00:00:01 | | S->P | BROADCAST |
|* 11 | TABLE ACCESS FULL| PRODUCTS | 1 | 22 | 19 (0)| 00:00:01 | | | |
| 12 | PX BLOCK ITERATOR | | 1677K| 41M| 11611 (4)| 00:00:01 | Q1,01 | PCWC | |
|* 13 | TABLE ACCESS FULL | SALE | 1677K| 41M| 11611 (4)| 00:00:01 | Q1,01 | PCWP | |
---------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
7 - access("S"."PROD_ID"="P"."PROD_ID")
11 - filter("PROD_CATEGORY"=:CTGY)
13 - filter(SYS_OP_BLOOM_FILTER(:BF0000,"S"."PROD_ID"))
Note
-----
- dynamic sampling used for this statement (level=2)
- 获取原sql的sql_id
SQL> SELECT sql_id, plan_hash_value, sql_text FROM V$SQL WHERE sql_text LIKE 'SELECT%parallel%prod_name, SUM(%';
SQL_ID PLAN_HASH_VALUE
------------- ---------------
SQL_TEXT
----------------------------------------------------------------------------------------------------------------------------------------------------
6hzb9zbyx9g59 1377251112
SELECT /*+ parallel(s) */ prod_name, SUM(amount_sold) FROM Sale s, Products p WHERE s.prod_id=p.prod_id AND prod_category = :ctgy GROUP BY prod_name
- Load the SQL to baseline
SQL> set serveroutput on
SQL> variable a_var number;
SQL> EXECUTE :a_var :=DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE(sql_id=>'&SQL_ID');
Enter value for sql_id: 6hzb9zbyx9g59
SQL> EXEC dbms_output.put_line('Number of plans loaded: ' || :a_var);
-- 检查load后情况
SQL> SELECT sql_handle, sql_text, plan_name, enabled FROM dba_sql_plan_baselines where sql_text LIKE '%SELECT%parallel%prod_name, SUM(%';
SQL_HANDLE SQL_TEXT PLAN_NAME ENA
------------------------------ -------------------------------------------------------------------------------- ------------------------------ ---
SQL_682437da01a36f66 SELECT /*+ parallel(s) */ prod_name, SUM(amount_sold) SQL_PLAN_6h91rv80u6vv63ec7f04c YES
FROM Sale s, Products p
- Disable the SQL in baseline
SQL> set serveroutput on
SQL> variable a_var number;
SQL> exec :a_var :=DBMS_SPM.ALTER_SQL_PLAN_BASELINE( -
SQL_HANDLE => '&SQL_HANDLE', -
PLAN_NAME => '&PLAN_NAME', -
ATTRIBUTE_NAME => 'enabled', -
ATTRIBUTE_VALUE => 'NO');
Enter value for sql_handle: SQL_682437da01a36f66
Enter value for plan_name: SQL_PLAN_6h91rv80u6vv63ec7f04c
PL/SQL procedure successfully completed.
SQL> EXEC dbms_output.put_line('Number of plans altered: ' || :a_var);
-- 检查disable后情况
SQL> SELECT sql_handle, sql_text, plan_name, enabled FROM dba_sql_plan_baselines where sql_text LIKE '%SELECT%parallel%prod_name, SUM(%';
SQL_HANDLE SQL_TEXT PLAN_NAME ENA
------------------------------ -------------------------------------------------------------------------------- ------------------------------ ---
SQL_682437da01a36f66 SELECT /*+ parallel(s) */ prod_name, SUM(amount_sold) SQL_PLAN_6h91rv80u6vv63ec7f04c NO
FROM Sale s, Products p
- Run the SQL with the target access path(使用串行执行计划)
SQL> variable ctgy varchar2(1);
SQL> exec :ctgy := 'K';
SQL> SELECT /*+ noparallel(s) */ prod_name, SUM(amount_sold)
FROM Sale s, Products p
WHERE s.prod_id=p.prod_id
AND prod_category = :ctgy
GROUP BY prod_name;
Execution Plan
----------------------------------------------------------
Plan hash value: 2656526082
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 145K| 6815K| 19071 (27)| 00:00:02 |
| 1 | HASH GROUP BY | | 145K| 6815K| 19071 (27)| 00:00:02 |
|* 2 | HASH JOIN | | 145K| 6815K| 18219 (24)| 00:00:02 |
|* 3 | TABLE ACCESS FULL| PRODUCTS | 1 | 22 | 19 (0)| 00:00:01 |
| 4 | TABLE ACCESS FULL| SALE | 1677K| 41M| 16929 (18)| 00:00:02 |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("S"."PROD_ID"="P"."PROD_ID")
3 - filter("PROD_CATEGORY"=:CTGY)
Note
-----
- dynamic sampling used for this statement (level=2)
- 找到目标sql的SQL_ID和PLAN_HASH_VALUE
SQL> SELECT sql_id, plan_hash_value, sql_text FROM V$SQL WHERE sql_text LIKE 'SELECT%noparallel%prod_name, SUM(%';
SQL_ID PLAN_HASH_VALUE
------------- ---------------
SQL_TEXT
----------------------------------------------------------------------------------------------------------------------------------------------------
455c1jzncuyh8 2656526082
SELECT /*+ noparallel */ prod_name, SUM(amount_sold) FROM Sale s, Products p WHERE s.prod_id=p.prod_id AND prod_category = :ctgy GROUP BY prod_name
- Associate the target plan with the Query
SQL> set serveroutput on
SQL> variable a_var number;
SQL> exec :a_var :=dbms_spm.load_plans_from_cursor_cache( -
sql_id => '&ORIGINAL_SQL_ID', -
plan_hash_value => &ORIGINAL_PLAN_HASH_VALUE, -
sql_handle => '&TARGET_SQL_HANDLE');
Enter value for original_sql_id: 455c1jzncuyh8
Enter value for original_plan_hash_value: 2656526082
Enter value for target_sql_handle: SQL_682437da01a36f66
PL/SQL procedure successfully completed.
SQL> EXEC dbms_output.put_line('Number of plans loaded: ' || :a_var);
- Check baseline
SQL> SELECT sql_handle, sql_text, plan_name, enabled FROM dba_sql_plan_baselines where sql_text LIKE 'SELECT%parallel%prod_name, SUM(%';
SQL_HANDLE SQL_TEXT PLAN_NAME ENA
------------------------------ -------------------------------------------------------------------------------- ------------------------------ ---
SQL_682437da01a36f66 SELECT /*+ parallel(s) */ prod_name, SUM(amount_sold) SQL_PLAN_6h91rv80u6vv63ec7f04c NO
FROM Sale s, Products p
WH
SQL_682437da01a36f66 SELECT /*+ parallel(s) */ prod_name, SUM(amount_sold) SQL_PLAN_6h91rv80u6vv6a4790fa7 YES
FROM Sale s, Products p
- Test that the original SQL runs serially
SQL> variable ctgy varchar2(1);
SQL> exec :ctgy := 'K';
PL/SQL procedure successfully completed.
SQL> set autotrace on explain
SQL> SELECT /*+ parallel(s) */ prod_name, SUM(amount_sold)
FROM Sale s, Products p
WHERE s.prod_id=p.prod_id
AND prod_category = :ctgy
GROUP BY prod_name;
Execution Plan
----------------------------------------------------------
Plan hash value: 2656526082
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 3904 | 183K| 11387 (2)| 00:00:01 |
| 1 | HASH GROUP BY | | 3904 | 183K| 11387 (2)| 00:00:01 |
|* 2 | HASH JOIN | | 3904 | 183K| 11384 (2)| 00:00:01 |
|* 3 | TABLE ACCESS FULL| PRODUCTS | 4 | 88 | 20 (5)| 00:00:01 |
| 4 | TABLE ACCESS FULL| SALE | 390K| 9913K| 11327 (1)| 00:00:01 |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("S"."PROD_ID"="P"."PROD_ID")
3 - filter("PROD_CATEGORY"=:CTGY)
Note
-----
- SQL plan baseline "SQL_PLAN_6h91rv80u6vv6a4790fa7" used for this statement
可以看到,此时即使执行带parallel hint的原语句也会使用串行执行计划,并且提示SQL plan baseline "SQL_PLAN_6h91rv80u6vv6a4790fa7" used for this statement
参考
Directing Plans with Baselines/Profiles Using coe_load_sql_baseline.sql / coe_load_sql_profile.sql (shipped with SQLT) ( Doc ID 1400903.1 )
Worked Example of Replacing the Current Existing Plan with a Different Plan Using a SPM SQL Plan Baseline ( Doc ID 1580762.1 )
SQL Plan Management (SPM) and/or SQL Plan Baseline Restrictions/Limitations ( Doc ID 2308153.1 )
https://mp.weixin.qq.com/s/oGMWmPQ8dKiRS0yA4VdA-g