0
点赞
收藏
分享

微信扫一扫

Oracle 如何不改变SQL为其绑定构造的执行计划


前面文章 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

举报

相关推荐

0 条评论