【DB笔试面试608】在Oracle中,如何使用STA来生成SQL Profile?

  • 2019 年 10 月 10 日
  • 筆記

题目部分

在Oracle中,如何使用STA来生成SQL Profile?

答案部分

利用STA对语句进行优化后,STA会对语句进行分析,采用最优的优化策略,并给出优化后的查询计划。可以按照STA给出的建议重写语句。但是,有些情况下,你可能无法重写语句(比如在生产环境中,SQL语句又在一个包中)。这个时候就可以利用Sql Profile,将优化策略存储在Profile中,Oracle在构建这条语句的查询计划时,就不会使用已有相关统计数据,而使用Profile的策略,生成新的查询计划。

第一步:给用户赋权限

[ZHLHRSPMDB2:oracle]:/oracle>sqlplus / as sysdba    SQL*Plus: Release 11.2.0.4.0 Production on Wed May 25 16:47:29 2016    Copyright (c) 1982, 2013, Oracle.  All rights reserved.      Connected to:  Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production  With the Partitioning, Real Application Clusters, OLAP, Data Mining  and Real Application Testing options    SYS@dlhr>  SYS@dlhr>  SYS@dlhr>  SYS@dlhr> GRANT CREATE ANY SQL PROFILE TO LHR;    Grant succeeded.    SYS@dlhr> GRANT DROP ANY SQL PROFILE TO LHR;    Grant succeeded.    SYS@dlhr> GRANT ALTER ANY SQL PROFILE TO LHR;    Grant succeeded.    SYS@dlhr> conn lhr/lhr  Connected.  LHR@dlhr>  LHR@dlhr> select * from v$version;    BANNER  --------------------------------------------------------------------------------  Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production  PL/SQL Release 11.2.0.4.0 - Production  CORE    11.2.0.4.0      Production  TNS for IBM/AIX RISC System/6000: Version 11.2.0.4.0 - Production  NLSRTL Version 11.2.0.4.0 - Production    LHR@dlhr> create table lhr.TB_LHR_20160525_01 as select * from dba_objects;    Table created.    LHR@dlhr> create index lhr.TB_LHR_20160525_01_idx on TB_LHR_20160525_01(object_id);    Index created.    LHR@dlhr> exec dbms_stats.gather_table_stats('lhr','TB_LHR_20160525_01',cascade=>true,degree=>4);    PL/SQL procedure successfully completed.    LHR@dlhr> set autot on  LHR@dlhr> select /*+no_index(TB_LHR_20160525_01 TB_LHR_20160525_01_idx)*/count(*) from lhr.TB_LHR_20160525_01 where object_id = 100 ;      COUNT(*)  ----------           1      Execution Plan  ----------------------------------------------------------  Plan hash value: 3612989399    -----------------------------------------------------------------------------------------  | Id  | Operation          | Name               | Rows  | Bytes | Cost (%CPU)| Time     |  -----------------------------------------------------------------------------------------  |   0 | SELECT STATEMENT   |                    |     1 |     5 |   351   (2)| 00:00:05 |  |   1 |  SORT AGGREGATE    |                    |     1 |     5 |            |          |  |*  2 |   TABLE ACCESS FULL| TB_LHR_20160525_01 |     1 |     5 |   351   (2)| 00:00:05 |  -----------------------------------------------------------------------------------------    Predicate Information (identified by operation id):  ---------------------------------------------------       2 - filter("OBJECT_ID"=100)      Statistics  ----------------------------------------------------------            1  recursive calls            0  db block gets         1249  consistent gets            0  physical reads            0  redo size          526  bytes sent via SQL*Net to client          520  bytes received via SQL*Net from client            2  SQL*Net roundtrips to/from client            0  sorts (memory)            0  sorts (disk)            1  rows processed  LHR@dlhr> set autot off  LHR@dlhr> SELECT v.SQL_ID, v.SQL_TEXT FROM v$sql v WHERE v.SQL_TEXT like '%no_index(TB_LHR_20160525_01%' and v.SQL_TEXT not like '%v$sql%' ;    SQL_ID  -------------  SQL_TEXT  ------------------------------------------------------------------------------------------------------------------------------------  7jt1btjkcczb8  select /*+no_index(TB_LHR_20160525_01 TB_LHR_20160525_01_idx)*/count(*) from lhr.TB_LHR_20160525_01 where object_id = 100    7suktf0w95cry  EXPLAIN PLAN SET STATEMENT_ID='PLUS150249' FOR select /*+no_index(TB_LHR_20160525_01 TB_LHR_20160525_01_idx)*/count(*) from lhr.TB_L  HR_20160525_01 where object_id = 100  

第二步:创建、执行优化任务

LHR@dlhr> DECLARE    2      my_task_name VARCHAR2(30);    3      my_sqltext       CLOB;    4  BEGIN    5      my_sqltext := 'select /*+no_index(TB_LHR_20160525_01 TB_LHR_20160525_01_idx)*/count(*) from lhr.TB_LHR_20160525_01 where object_id = 100';    6      my_task_name := DBMS_SQLTUNE.CREATE_TUNING_TASK(    7                              sql_text          => my_sqltext,    8                              user_name       => 'LHR',    9                              scope           => 'COMPREHENSIVE',   10                              time_limit    => 60,   11                              task_name       => 'sql_profile_test',   12                              description => 'Task to tune a query on a specified table');   13      DBMS_SQLTUNE.EXECUTE_TUNING_TASK( task_name => 'sql_profile_test');   14  END;   15  /    PL/SQL procedure successfully completed.  

或者也可以使用sqlid来生成优化任务,如下:

LHR@dlhr> DECLARE    2    a_tuning_task VARCHAR2(30);    3  BEGIN    4    a_tuning_task := dbms_sqltune.create_tuning_task(sql_id    => '7jt1btjkcczb8',    5                                                     task_name => 'sql_profile_test_SQLID');    6    dbms_sqltune.execute_tuning_task(a_tuning_task);    7  END;    8  /    PL/SQL procedure successfully completed.  

第三步:查看优化建议

LHR@dlhr> set autot off  LHR@dlhr> set long 10000  LHR@dlhr> set longchunksize 1000  LHR@dlhr> set linesize 100  LHR@dlhr> SELECT DBMS_SQLTUNE.REPORT_TUNING_TASK( 'sql_profile_test') from DUAL;    DBMS_SQLTUNE.REPORT_TUNING_TASK('SQL_PROFILE_TEST')  ----------------------------------------------------------------------------------------------------  GENERAL INFORMATION SECTION  -------------------------------------------------------------------------------  Tuning Task Name   : sql_profile_test  Tuning Task Owner  : LHR  Workload Type      : Single SQL Statement  Scope              : COMPREHENSIVE  Time Limit(seconds): 60  Completion Status  : COMPLETED  Started at         : 05/25/2016 16:58:31  Completed at       : 05/25/2016 16:58:32    -------------------------------------------------------------------------------  Schema Name: LHR  SQL ID     : 9kzm8scz6t92z  SQL Text   : select /*+no_index(TB_LHR_20160525_01               TB_LHR_20160525_01_idx)*/count(*) from lhr.TB_LHR_20160525_01               where object_id = 100    -------------------------------------------------------------------------------  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: 99.83%)    ------------------------------------------    - Consider accepting the recommended SQL profile.      execute dbms_sqltune.accept_sql_profile(task_name => 'sql_profile_test',              task_owner => 'LHR', replace => TRUE);      Validation results    ------------------    The SQL profile was tested by executing both its plan and the original plan    and measuring their respective execution statistics. A plan may have been    only partially executed if the other could be run to completion in less time.                               Original Plan  With SQL Profile  % Improved                             -------------  ----------------  ----------    Completion Status:            COMPLETE          COMPLETE    Elapsed Time (s):             .006278            .00004      99.36 %    CPU Time (s):                 .003397           .000021      99.38 %    User I/O Time (s):                  0                 0    Buffer Gets:                     1249                 2      99.83 %    Physical Read Requests:             0                 0    Physical Write Requests:            0                 0    DBMS_SQLTUNE.REPORT_TUNING_TASK('SQL_PROFILE_TEST')  ----------------------------------------------------------------------------------------------------    Physical Read Bytes:                0                 0    Physical Write Bytes:               0                 0    Rows Processed:                     1                 1    Fetches:                            1                 1    Executions:                         1                 1      Notes    -----    1. Statistics for the original plan were averaged over 10 executions.    2. Statistics for the SQL profile plan were averaged over 10 executions.    -------------------------------------------------------------------------------  EXPLAIN PLANS SECTION  -------------------------------------------------------------------------------    1- Original With Adjusted Cost  ------------------------------  Plan hash value: 3612989399    -----------------------------------------------------------------------------------------  | Id  | Operation          | Name               | Rows  | Bytes | Cost (%CPU)| Time     |  -----------------------------------------------------------------------------------------  |   0 | SELECT STATEMENT   |                    |     1 |     5 |   351   (2)| 00:00:05 |  |   1 |  SORT AGGREGATE    |                    |     1 |     5 |            |          |  |*  2 |   TABLE ACCESS FULL| TB_LHR_20160525_01 |     1 |     5 |   351   (2)| 00:00:05 |  -----------------------------------------------------------------------------------------    Predicate Information (identified by operation id):  ---------------------------------------------------       2 - filter("OBJECT_ID"=100)    2- Using SQL Profile  --------------------  Plan hash value: 661515879    --------------------------------------------------------------------------------------------  | Id  | Operation         | Name                   | Rows  | Bytes | Cost (%CPU)| Time     |  --------------------------------------------------------------------------------------------  |   0 | SELECT STATEMENT  |                        |     1 |     5 |     1   (0)| 00:00:01 |  |   1 |  SORT AGGREGATE   |                        |     1 |     5 |            |          |  |*  2 |   INDEX RANGE SCAN| TB_LHR_20160525_01_IDX |     1 |     5 |     1   (0)| 00:00:01 |  --------------------------------------------------------------------------------------------    Predicate Information (identified by operation id):  ---------------------------------------------------      DBMS_SQLTUNE.REPORT_TUNING_TASK('SQL_PROFILE_TEST')  ----------------------------------------------------------------------------------------------------     2 - access("OBJECT_ID"=100)    -------------------------------------------------------------------------------  

这里可以看到,在优化建议中给出了新的查询计划。现在,我们决定接受这个建议,并且不重写语句。

第四步:接受profile

LHR@dlhr> set autot on  LHR@dlhr> select /*+no_index(TB_LHR_20160525_01 TB_LHR_20160525_01_idx)*/count(*) from lhr.TB_LHR_20160525_01 where object_id = 100 ;      COUNT(*)  ----------           1      Execution Plan  ----------------------------------------------------------    Plan hash value: 3612989399    -----------------------------------------------------------------------------------------  | Id  | Operation          | Name               | Rows  | Bytes | Cost (%CPU)| Time     |  -----------------------------------------------------------------------------------------  |   0 | SELECT STATEMENT   |                    |     1 |     5 |   351   (2)| 00:00:05 |  |   1 |  SORT AGGREGATE    |                    |     1 |     5 |            |          |  |*  2 |   TABLE ACCESS FULL| TB_LHR_20160525_01 |     1 |     5 |   351   (2)| 00:00:05 |  -----------------------------------------------------------------------------------------    Predicate Information (identified by operation id):  ---------------------------------------------------       2 - filter("OBJECT_ID"=100)      Statistics  ----------------------------------------------------------            0  recursive calls            0  db block gets         1249  consistent gets            0  physical reads            0  redo size          526  bytes sent via SQL*Net to client          520  bytes received via SQL*Net from client            2  SQL*Net roundtrips to/from client            0  sorts (memory)            0  sorts (disk)            1  rows processed  LHR@dlhr> execute dbms_sqltune.accept_sql_profile(task_name =>'sql_profile_test_SQLID', task_owner => 'LHR', replace => TRUE);    PL/SQL procedure successfully completed.    LHR@dlhr> set autot off  LHR@dlhr>  SELECT e.task_name, b.name, d.sql_text, extractvalue(value(h), '.') as hints    2     FROM dba_sql_profiles d,    3           dba_advisor_tasks e,    4          SYS.SQLOBJ$DATA A,    5          SYS.SQLOBJ$ B,    6          TABLE(XMLSEQUENCE(EXTRACT(XMLTYPE(A.COMP_DATA),    7                                    '/outline_data/hint'))) h    8    where a.signature = b.signature    9      and a.category = b.category   10      and a.obj_type = b.obj_type   11      and a.plan_id = b.plan_id   12      and a.signature = d.signature   13      and d.task_id=e.task_id   14      and d.name = 'SYS_SQLPROF_0154e728ad3f0000'   15     ;    TASK_NAME                      NAME  ------------------------------ ------------------------------  SQL_TEXT  ----------------------------------------------------------------------------------------------------  HINTS  ----------------------------------------------------------------------------------------------------  sql_profile_test               SYS_SQLPROF_0154e728ad3f0000  select /*+no_index(TB_LHR_20160525_01 TB_LHR_20160525_01_idx)*/count(*) from lhr.TB_LHR_20160525_01  where object_id = 100  OPTIMIZER_FEATURES_ENABLE(default)    sql_profile_test               SYS_SQLPROF_0154e728ad3f0000  select /*+no_index(TB_LHR_20160525_01 TB_LHR_20160525_01_idx)*/count(*) from lhr.TB_LHR_20160525_01  where object_id = 100  IGNORE_OPTIM_EMBEDDED_HINTS  

在这里用了包DBMS_SQLTUNE的另一个函数:ACCEPT_SQL_PROFILE。其中,参数task_name即我们创建的优化建议任务的名称,name是profile的名字,可以是任意合法名称。此外这个函数还有其他一些函数,下面是这个函数的原型:

DBMS_SQLTUNE.ACCEPT_SQL_PROFILE (     task_name    IN  VARCHAR2,     object_id    IN  NUMBER   := NULL,     name         IN  VARCHAR2 := NULL,     description  IN  VARCHAR2 := NULL,     category     IN  VARCHAR2 := NULL;     task_owner   IN VARCHAR2  := NULL,     replace      IN BOOLEAN   := FALSE,     force_match  IN BOOLEAN   := FALSE)   RETURN VARCHAR2;  

Description是profile的描述信息;task_owner是优化建议任务的所有者;replace为TRUE时,如果这个profile已经存在,就代替它;force_match为TURE时,表示与语句强制匹配,即强制使用绑定变量,和系统参数cursor_sharing设置为FORCE时类似,为FALSE时,与cursor_sharing设置为EXACT时类似,即完全匹配。

这里要特别提到的是category这个参数,你可以通过设置这个参数,制定特定会话使用这个profile。在10g中,每个会话都有一个新参数SQLTUNE_CATEGORY,他的默认值是DEFAULT。而我们在调用这个函数时,如果没有指定这个参数,那它的值也是DEFAULT,而如果我们给这个profile指定了一个其它的CATEGORY值,如FOR_TUNING,那么只有会话参SQLTUNE_CATEGORY也为FOR_TUNING时,才会使用这个porfile。为什么说这个参数很有用呢?试想一个这样的环境:你在一个生产系统上利用STA调优一条语句,STA已经给出了优化建议,但是你又不敢贸然实施它给出的建议(毕竟它只是机器嘛,不能完全信任),你就可以创建一个有特殊CATEGORY的profile,然后在你自己的会话中制定SQLTUNE_CATEGORY为这个特殊的CATEGORY,那就既可以看优化建议的实际效果又不影响生产环境。

此外可以通过视图DBA_SQL_PROFILES来查看已经创建的profile。

第五步:查看profile的效果

LHR@dlhr> set autot on  LHR@dlhr> select /*+no_index(TB_LHR_20160525_01 TB_LHR_20160525_01_idx)*/count(*) from lhr.TB_LHR_20160525_01 where object_id = 100 ;      COUNT(*)  ----------           1      Execution Plan  ----------------------------------------------------------  Plan hash value: 661515879    --------------------------------------------------------------------------------------------  | Id  | Operation         | Name                   | Rows  | Bytes | Cost (%CPU)| Time     |  --------------------------------------------------------------------------------------------  |   0 | SELECT STATEMENT  |                        |     1 |     5 |     1   (0)| 00:00:01 |  |   1 |  SORT AGGREGATE   |                        |     1 |     5 |            |          |  |*  2 |   INDEX RANGE SCAN| TB_LHR_20160525_01_IDX |     1 |     5 |     1   (0)| 00:00:01 |  --------------------------------------------------------------------------------------------    Predicate Information (identified by operation id):  ---------------------------------------------------       2 - access("OBJECT_ID"=100)    Note  -----     - SQL profile "SYS_SQLPROF_0154e728ad3f0000" used for this statement      Statistics  ----------------------------------------------------------            1  recursive calls            0  db block gets            2  consistent gets            0  physical reads            0  redo size          526  bytes sent via SQL*Net to client          520  bytes received via SQL*Net from client            2  SQL*Net roundtrips to/from client            0  sorts (memory)            0  sorts (disk)            1  rows processed  

从NOTE部分可以看到,语句采用了profile中的数据,创建了新的查询计划。并且在查询计划中还有一些附加信息,表明这个语句是采用了“SYS_SQLPROF_0154e728ad3f0000”这个Profile,而不是根据对象上面的统计数据来生成的查询计划。

但上述方法主要是依赖sql tuning advisor,如果它无法生成你想要的执行计划.你还可以通过手动的方式,通过sql profile把hint加进去.复杂的SQL的hint可以采用脚本coe_xfr_sql_profile.sql来产生原语句的outline data和加hint语句的outline data,然后替换对应的SYS.SQLPROF_ATTR,最后执行生成的sql就可以了。

使用PLSQL DEVELOPER 11查看执行计划,如下图,新版本的好处:

本文选自《Oracle程序员面试笔试宝典》,作者:李华荣。

Exit mobile version