sql patch和sql profile 解决Index join SQL性能故障

前面的方法我们还原了问题现象,因为CBO使用错误的统计信息,使用了INDEX_JOIN的方式, 现在尝试如何快速的解决该问题,也就是禁用INDEX_JOIN或使用我们指定的执行计划。

下面会使用以下几种方法来修正执行计划:
sql profile + hint
sql profile + parameter
set index part stats
sql patch + hint

数据存储在 SQLOBJ 开头的internal table, 如SYS.SQLOBJ$, SYS.SQLOBJ$PLAN, SYS.SQLOBJ$DATA, and SYS.SQLOBJ$AUXDATA. 

SYS.SQLOBJ$ stores SQL Patch, SQL Profile and SQL Plan Baseline Data for a specific sql statement

下面举个例子

 

SQL> explain plan for SELECT    /*anbob*/   ATTRID,              
ATTRVALUE   FROM       anbob.ANBOB_T1
    WHERE       RECOID = :RECOID    AND       REGION = 399;

Explained.

SQL> @x2

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------------------------------------
Plan hash value: 3978804789

-------------------------------------------------------------------------------------------------------------------
| Id  | Operation                  | Name                 | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT           |                      |   985K|   178M|   197   (1)| 00:00:03 |       |       |
|*  1 |  VIEW                      | index$_join$_001     |   985K|   178M|   197   (1)| 00:00:03 |       |       |
|*  2 |   HASH JOIN                |                      |       |       |            |          |       |       |
|   3 |    PARTITION RANGE ITERATOR|                      |   985K|   178M|   136  (92)| 00:00:02 |    65 |    74 |
|*  4 |     INDEX RANGE SCAN       | IDX_ANBOB_T1         |   985K|   178M|   136  (92)| 00:00:02 |    65 |    74 |
|   5 |    PARTITION RANGE ITERATOR|                      |   985K|   178M|   232   (1)| 00:00:03 |    65 |    74 |
|*  6 |     INDEX FAST FULL SCAN   | IDX_ANBOB_T1_VALUE   |   985K|   178M|   232   (1)| 00:00:03 |    65 |    74 |
-------------------------------------------------------------------------------------------------------------------

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

   1 - filter("RECOID"=TO_NUMBER(:RECOID))
   2 - access(ROWID=ROWID)
   4 - access("RECOID"=TO_NUMBER(:RECOID))
   6 - filter("REGION"=399)
   
Note
-----
- dynamic sampling used for this statement (level=2)

25 rows selected.

note:
上面使用了INDEX JOIN的方法,我们发现之前使用的索引只是IDX_ANBOB_T1,并且效率比现在的index_join要高,下面尝试使用SQL中加HINT的方式对比一下效果

# 使用手动加no_index hint方法

SQL> explain plan for SELECT   /*+no_index(ANBOB_T1 IDX_ANBOB_T1_VALUE)*/    ATTRID,              ATTRVALUE  
 FROM       anbob.ANBOB_T1
 WHERE       RECOID = :RECOID    AND       REGION = 399;

Explained.

SQL> @x2

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------
Plan hash value: 491038779

----------------------------------------------------------------------------------------------------------------------
| Id  | Operation                          | Name            | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                   |                 |   985K|   178M|  3104   (1)| 00:00:38 |       |       |
|   1 |  PARTITION RANGE ITERATOR          |                 |   985K|   178M|  3104   (1)| 00:00:38 |    65 |    74 |
|*  2 |   TABLE ACCESS BY LOCAL INDEX ROWID| ANBOB_T1        |   985K|   178M|  3104   (1)| 00:00:38 |    65 |    74 |
|*  3 |    INDEX RANGE SCAN                | IDX_ANBOB_T1    |  5637K|       |    11   (0)| 00:00:01 |    65 |    74 |
----------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("REGION"=399)
   3 - access("RECOID"=TO_NUMBER(:RECOID))

Note
-----
   - dynamic sampling used for this statement (level=2)

Note:
手动用hint no_index(tablename indexname)的方法是可以禁用index_join的

-- 取到SQL_ID ,下面使用SQL PROFILE 的方法尝试禁用index_join
SQL> @sqlt anbob
HASH_VALUE SQL_ID             CHLD# OPT_MODE   SQL_TEXT
---------- ------------- ---------- ---------- ----------------------------------------------------------------------------------------------------
 983567814 03j7fz4xa03f6          0 ALL_ROWS   SELECT    /*anbob*/   ATTRID,              ATTRVALUE   FROM       anbob.ANBOB_T1     WHERE
                                               RECOID = :RECOID    AND       REGION = 399

3292410184 gbr0tjv23wca8          0 ALL_ROWS   EXPLAIN PLAN SET STATEMENT_ID='PLUS4294967295' FOR SELECT    /*anbob*/   ATTRID,
                                               ATTRVALUE   FROM       anbob.ANBOB_T1     WHERE       RECOID = :RECOID    AND       REGION = 399
SQL> DECLARE
  2    SQL_FTEXT CLOB;
  3   BEGIN
  4   SELECT SQL_FULLTEXT INTO SQL_FTEXT FROM V$SQLAREA WHERE SQL_ID = '03j7fz4xa03f6';
  5   
  6   DBMS_SQLTUNE.IMPORT_SQL_PROFILE(
  7     SQL_TEXT => SQL_FTEXT,
  8     PROFILE => SQLPROF_ATTR('no_index(ANBOB_T1 IDX_ANBOB_T1_VALUE)'),
  9     NAME => 'PROFILE_03j7fz4xa03f6',
 10     REPLACE => TRUE,
 11     FORCE_MATCH => TRUE
 12   );
 13   END;
 14  /

PL/SQL procedure successfully completed.


SQL> set autot trace exp 
SQL> SELECT    /*anbob*/   ATTRID,              ATTRVALUE   FROM       anbob.ANBOB_T1
  2          WHERE       RECOID = :RECOID    AND       REGION = 399;

no rows selected

Execution Plan
----------------------------------------------------------
Plan hash value: 3978804789

-------------------------------------------------------------------------------------------------------------------
| Id  | Operation                  | Name                 | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT           |                      |  1362K|   246M|   197   (1)| 00:00:03 |       |       |
|*  1 |  VIEW                      | index$_join$_001     |  1362K|   246M|   197   (1)| 00:00:03 |       |       |
|*  2 |   HASH JOIN                |                      |       |       |            |          |       |       |
|   3 |    PARTITION RANGE ITERATOR|                      |  1362K|   246M|   183  (94)| 00:00:03 |    65 |    74 |
|*  4 |     INDEX RANGE SCAN       | IDX_ANBOB_T1         |  1362K|   246M|   183  (94)| 00:00:03 |    65 |    74 |
|   5 |    PARTITION RANGE ITERATOR|                      |  1362K|   246M|   232   (1)| 00:00:03 |    65 |    74 |
|*  6 |     INDEX FAST FULL SCAN   | IDX_ANBOB_T1_VALUE   |  1362K|   246M|   232   (1)| 00:00:03 |    65 |    74 |
-------------------------------------------------------------------------------------------------------------------

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

   1 - filter("RECOID"=TO_NUMBER(:RECOID))
   2 - access(ROWID=ROWID)
   4 - access("RECOID"=TO_NUMBER(:RECOID))
   6 - filter("REGION"=399)

Note
-----
   - dynamic sampling used for this statement (level=2)
   - SQL profile "PROFILE_03j7fz4xa03f6" used for this statement

Note:
使用sql profile的no_index(tablename indexname)的方法没有成功,原因后面会说。在Twitter上请问了JL大师,建议使用no_index具体的scan方式试试

SQL> DECLARE
  2    SQL_FTEXT CLOB;
  3   BEGIN
  4   SELECT SQL_FULLTEXT INTO SQL_FTEXT FROM V$SQLAREA WHERE SQL_ID = '03j7fz4xa03f6';
  5   
  6   DBMS_SQLTUNE.IMPORT_SQL_PROFILE(
  7     SQL_TEXT => SQL_FTEXT,
  8     PROFILE => SQLPROF_ATTR('NO_INDEX_FFS(ANBOB_T1 "IDX_ANBOB_T1_VALUE")'),
  9     NAME => 'PROFILE_03j7fz4xa03f6',
 10     REPLACE => TRUE,
 11     FORCE_MATCH => TRUE
 12   );
 13   END;
 14  /

PL/SQL procedure successfully completed.

SQL> SELECT    /*anbob*/   ATTRID,              ATTRVALUE   FROM       anbob.ANBOB_T1
  2          WHERE       RECOID = :RECOID    AND       REGION = 399;
no rows selected

Execution Plan
----------------------------------------------------------
Plan hash value: 3978804789
-------------------------------------------------------------------------------------------------------------------
| Id  | Operation                  | Name                 | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT           |                      |  1362K|   246M|   197   (1)| 00:00:03 |       |       |
|*  1 |  VIEW                      | index$_join$_001     |  1362K|   246M|   197   (1)| 00:00:03 |       |       |
|*  2 |   HASH JOIN                |                      |       |       |            |          |       |       |
|   3 |    PARTITION RANGE ITERATOR|                      |  1362K|   246M|   183  (94)| 00:00:03 |    65 |    74 |
|*  4 |     INDEX RANGE SCAN       | IDX_ANBOB_T1         |  1362K|   246M|   183  (94)| 00:00:03 |    65 |    74 |
|   5 |    PARTITION RANGE ITERATOR|                      |  1362K|   246M|   232   (1)| 00:00:03 |    65 |    74 |
|*  6 |     INDEX FAST FULL SCAN   | IDX_ANBOB_T1_VALUE   |  1362K|   246M|   232   (1)| 00:00:03 |    65 |    74 |
-------------------------------------------------------------------------------------------------------------------

NOTE:
使用no_index_ffs(tablename indexname)的profile也未成功。又使用了no_index 指定字段的方式

SQL> DECLARE
  2    SQL_FTEXT CLOB;
  3   BEGIN
  4   SELECT SQL_FULLTEXT INTO SQL_FTEXT FROM V$SQLAREA WHERE SQL_ID = '03j7fz4xa03f6';
  5   
  6   DBMS_SQLTUNE.IMPORT_SQL_PROFILE(
  7     SQL_TEXT => SQL_FTEXT,
  8     PROFILE => SQLPROF_ATTR('NO_INDEX(ANBOB_T1 (ATTRID,ATTRVALUE,REGION))'),
  9     NAME => 'PROFILE_03j7fz4xa03f6',
 10     REPLACE => TRUE,
 11     FORCE_MATCH => TRUE
 12   );
 13   END;
 14  /

PL/SQL procedure successfully completed.

SQL> SELECT    /*anbob*/   ATTRID,              ATTRVALUE   FROM       anbob.ANBOB_T1
  2          WHERE       RECOID = :RECOID    AND       REGION = 399;

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 3978804789

-------------------------------------------------------------------------------------------------------------------
| Id  | Operation                  | Name                 | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT           |                      |  1362K|   246M|   197   (1)| 00:00:03 |       |       |
|*  1 |  VIEW                      | index$_join$_001     |  1362K|   246M|   197   (1)| 00:00:03 |       |       |
|*  2 |   HASH JOIN                |                      |       |       |            |          |       |       |
|   3 |    PARTITION RANGE ITERATOR|                      |  1362K|   246M|   183  (94)| 00:00:03 |    65 |    74 |
|*  4 |     INDEX RANGE SCAN       | IDX_ANBOB_T1         |  1362K|   246M|   183  (94)| 00:00:03 |    65 |    74 |
|   5 |    PARTITION RANGE ITERATOR|                      |  1362K|   246M|   232   (1)| 00:00:03 |    65 |    74 |
|*  6 |     INDEX FAST FULL SCAN   | IDX_ANBOB_T1_VALUE   |  1362K|   246M|   232   (1)| 00:00:03 |    65 |    74 |
-------------------------------------------------------------------------------------------------------------------

Note:
使用SQL PROFILE 使用no_index(tablename (column_name,…))的方法也没有成功,实际上这个方法也是可行的。 其实是被绕进去了,并不是no_index的问题,而是sql profile的hint的用法, 需要使用query block name. 这里先用sel$ 默认的尝试

SQL> DECLARE
  2    SQL_FTEXT CLOB;
  3   BEGIN
  4   SELECT SQL_FULLTEXT INTO SQL_FTEXT FROM V$SQLAREA WHERE SQL_ID = '03j7fz4xa03f6';
  5   
  6   DBMS_SQLTUNE.IMPORT_SQL_PROFILE(
  7     SQL_TEXT => SQL_FTEXT,
  8     PROFILE => SQLPROF_ATTR('NO_INDEX(@sel$1  ANBOB_T1@sel$1 "IDX_ANBOB_T1_VALUE")'),
  9     NAME => 'PROFILE_03j7fz4xa03f6',
 10     REPLACE => TRUE,
 11     FORCE_MATCH => TRUE
 12   );
 13   END;
 14  /
PL/SQL procedure successfully completed.

SQL> SELECT    /*anbob*/   ATTRID,              ATTRVALUE   
FROM       anbob.ANBOB_T1
 WHERE       RECOID = :RECOID    AND       REGION = 399;
no rows selected
Execution Plan
----------------------------------------------------------
Plan hash value: 491038779

----------------------------------------------------------------------------------------------------------------------
| Id  | Operation                          | Name            | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                   |                 |  1362K|   246M|  3149   (1)| 00:00:38 |       |       |
|   1 |  PARTITION RANGE ITERATOR          |                 |  1362K|   246M|  3149   (1)| 00:00:38 |    65 |    74 |
|*  2 |   TABLE ACCESS BY LOCAL INDEX ROWID| ANBOB_T1        |  1362K|   246M|  3149   (1)| 00:00:38 |    65 |    74 |
|*  3 |    INDEX RANGE SCAN                | IDX_ANBOB_T1    |  7792K|       |    11   (0)| 00:00:01 |    65 |    74 |
----------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("REGION"=399)
   3 - access("RECOID"=TO_NUMBER(:RECOID))

Note
-----
   - dynamic sampling used for this statement (level=2)
   - SQL profile "PROFILE_03j7fz4xa03f6" used for this statement

Note:
这次使用profile no_index(sel$ indexname)的方式成功禁掉了其中一个索引,间接禁用了index_join,上面失败的原因就在这里,no_index(tablealiasname indexname)是这次我们要使用的方法,因为开始表中没有使用别名,而且query block在的名字因为index_join的原因转换的更加特殊,开始忽略了query block的名字,为什么使用在SQL 中hint的方法可以呢?如果hint 写在sql中,SQL在解析阶段会识别到,ORACLE 优化器在hint有效的情况就可以实现hint的意图,如果使用sql profile的方式增加的hint,那是不属于SQL PARSE的一部分,而是SQL查询转换后,干扰QUERY BLOCK执行计划部分的行为,所以使用sql profile加hint的方式对于no_index应该是 no_index(query_block_name obj_alias_name@query_block_name index_name), 以前的blog中也写过可以从v$sql_plan找到query block name.

oracle系统生成的query block有一些规律,比如select通常会以sel$前头加数字序列,如上面的sel$1,下面是一些query blcok前缀列表:
Prefix Used For
CRI$ CREATE INDEX statements
DEL$ DELETE statements
INS$ INSERT statements
MISC$ Miscellaneous SQL statements like LOCK TABLE
MRG$ MERGE statements
SEL$ SELECT statements
SET$ Set operators like UNION and MINUS
UPD$ UPDATE statements

对于本次index_join 时的query block可能不是sel$1,有时oracle系统生成的block可能会是sel$跟8位的hash值,因为这是查询转换后的query block,有些是不可控的. 对于本案例如果加上query block name在sql profile中,
PROFILE => SQLPROF_ATTR(‘INDEX_RS_ASC(@”SEL$1″ “ANBOB_T1″@”SEL$1″ (“ANBOB_T1″.”RECOID”))’),PROFILE => SQLPROF_ATTR(‘NO_INDEX(@sel$1 ANBOB_T1@sel$1 (ATTRID,ATTRVALUE,REGION))’) 等方法都是可行的。

第二个方法SQL PROFILE把该参数禁掉

index_join 还有一个CBO的系统参数可以禁用该特性,下面是使用SQL PROFILE把该参数禁掉实现disable index_join

SQL> DECLARE
  2    SQL_FTEXT CLOB;
  3   BEGIN
  4   SELECT SQL_FULLTEXT INTO SQL_FTEXT FROM V$SQLAREA WHERE SQL_ID = '03j7fz4xa03f6';
  5   
  6   DBMS_SQLTUNE.IMPORT_SQL_PROFILE(
  7     SQL_TEXT => SQL_FTEXT,
  8     PROFILE => SQLPROF_ATTR(' opt_param(''_index_join_enabled'',''false'') '),
  9     NAME => 'PROFILE_03j7fz4xa03f6',
 10     REPLACE => TRUE,
 11     FORCE_MATCH => TRUE
 12   );
 13   END;
 14  /

PL/SQL procedure successfully completed.

SQL> SELECT    /*anbob*/   ATTRID,              ATTRVALUE   FROM       anbob.ANBOB_T1
  2          WHERE       RECOID = :RECOID    AND       REGION = 399;

no rows selected
Execution Plan
----------------------------------------------------------
Plan hash value: 491038779

----------------------------------------------------------------------------------------------------------------------
| Id  | Operation                          | Name            | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                   |                 |  1362K|   246M|  3149   (1)| 00:00:38 |       |       |
|   1 |  PARTITION RANGE ITERATOR          |                 |  1362K|   246M|  3149   (1)| 00:00:38 |    65 |    74 |
|*  2 |   TABLE ACCESS BY LOCAL INDEX ROWID| ANBOB_T1     |  1362K|   246M|  3149   (1)| 00:00:38 |    65 |    74 |
|*  3 |    INDEX RANGE SCAN                | IDX_ANBOB_T1 |  7792K|       |    11   (0)| 00:00:01 |    65 |    74 |
----------------------------------------------------------------------------------------------------------------------

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

   2 - filter("REGION"=399)
   3 - access("RECOID"=TO_NUMBER(:RECOID))

Note
-----
   - dynamic sampling used for this statement (level=2)
   - SQL profile "PROFILE_03j7fz4xa03f6" used for this statement

第三个方法用set index stats

修改统计信息更接近真实的方法也是可行的,有时因为分区过大,短时间可能无法收集全部分区,可以先收集一个分区然后复制到其它分区(数据分布相差不大)
比如复制月份的分区

SQL> DECLARE
  2     v_numrows   NUMBER := 16915333;
  3     v_numlblks   NUMBER := 129733;
  4     v_numdist   NUMBER := 9424;
  5     v_clstfct   NUMBER := 1376533;
  6     v_indlevel  NUMBER := 4;
  7     v_partend   DATE := DATE '2016-4-1';                              -- ??????
  8     v_partcur   DATE := DATE '2015-7-1';                              -- ??????
  9     v_sql       VARCHAR2 (100);
 10  BEGIN
 11     WHILE v_partcur < v_partend  
12     LOOP  
13        v_partcur := ADD_MONTHS (v_partcur, 1); 
          v_sql := 'PART_399_' || TO_CHAR (ADD_MONTHS (v_partcur, -1), 'yyyymm');  
          DBMS_STATS.set_index_STATS (ownname    => 'anbob',
 18                                    indname    => 'IDX_ANBOB_T1_VALUE',
 19                                    partname   => v_sql,
 20                                    numrows    => v_numrows,
 21                                    numlblks    => v_numlblks,
 22                                    numdist    => v_numdist,
 23                                                               clstfct    => v_clstfct,
 24                                                               indlevel   => v_indlevel,
 25                                                               no_invalidate=>false);
 26        DBMS_OUTPUT.put_line (v_sql);
 27     END LOOP;
 28  END;
 29  /

TIP:

In Oracle 10g the default for the NO_INVALIDATE parameter is now AUTO_INVALIDATE.
This means that Oracle will not immediately invalidate the cached cursors on gathering of fresh statistics, but wait for a period of time to elapse first.This period of time is controlled by the parameter _optimizer_invalidation_period which defaults to a value of 18000 (seconds) or 5 hours.

第四个方法,使用SQL PATCH的方法

SQL> begin
  2    DBMS_SQLDIAG.DROP_SQL_PATCH(name  => 'test_no_index_patch');
  3  end;
  4  /

PL/SQL procedure successfully completed.

SQL> DECLARE
  2    SQL_T varchar2(1000);
  3   BEGIN
  4   SELECT SQL_TEXT INTO SQL_T FROM V$SQLAREA WHERE SQL_ID = '03j7fz4xa03f6';   
  5       dbms_sqldiag_internal.i_create_patch(
  6                           sql_text => SQL_T,
  7                           hint_text => 'no_index(@"SEL$1" "ANBOB_T1"@"SEL$1" IDX_ANBOB_T1_VALUE)',
  8                           name  => 'test_no_index_patch');
  9  end;
 10  /

PL/SQL procedure successfully completed.

SQL> SELECT    /*anbob*/   ATTRID,              ATTRVALUE   FROM       ANBOB.ANBOB_T1
  2          WHERE       RECOID = :RECOID    AND       REGION = 315;

no rows selected
Execution Plan
----------------------------------------------------------
Plan hash value: 491038779

----------------------------------------------------------------------------------------------------------------------
| Id  | Operation                          | Name            | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                   |                 |  1362K|   246M|  3149   (1)| 00:00:38 |       |       |
|   1 |  PARTITION RANGE ITERATOR          |                 |  1362K|   246M|  3149   (1)| 00:00:38 |    65 |    74 |
|*  2 |   TABLE ACCESS BY LOCAL INDEX ROWID| ANBOB_T1        |  1362K|   246M|  3149   (1)| 00:00:38 |    65 |    74 |
|*  3 |    INDEX RANGE SCAN                | IDX_ANBOB_T1    |  7792K|       |    11   (0)| 00:00:01 |    65 |    74 |
----------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("REGION"=399)
   3 - access("RECOID"=TO_NUMBER(:RECOID))

Note
-----
   - dynamic sampling used for this statement (level=2)
   - SQL patch "test_no_index_patch" used for this statement

Note:
SQL PATCH 和SQL profile一样也可以在不修改SQL TEXT的情况下,增加HINT固定执行计划, SQL PATCH 也是包含在EE版本中,不需要购买额外的License.

SQL PATCH介绍

SQL 补丁是使用 DBMS_SQLDIAG 包创建的。它们在优化器级别应用提示,影响执行计划的生成方式。与 SQL PROFILE 不同,SQL PROFILE 调整优化器的估计值,而 SQL PATCH 则明确修改优化器的决策过程,但不像 SQL baseline 那样直接强制执行计划。

当有问题的SQL语句需要特定的优化器提示以获得更好的性能时,或者当处理无法修改SQL代码的第三方应用程序时,SQL Patch是一种方法。

通过SQL补丁添加提示从Oracle Database 12c Release 2开始可用,使用DBMS_SQLDIAG.CREATE_SQL_PATCH。也可以在11g中使用一个未记录的名为DBMS_SQLDIAG_INTERNAL.I_CREATE_PATCH的过程来完成。

sql patch创建

-- The DBMS_SQLDIAG_INTERNAL.I_CREATE_PATCH actually requires the SQL Text (as a CLOB) instead of just
-- the SQL_ID, in 12.1 and earlier this script fetches the SQL Text from v$sql (the cursor needs
-- to be in library cache). Similarly the DBMS_SQLDIAG.CREATE_PATCH(sql_id=>...) needs the cursor to be
-- in library cache in order to find the corresponding SQL text.

SET SERVEROUT ON SIZE 1000000

DECLARE
    v_sql_text  CLOB;
    ret         VARCHAR2(100);
BEGIN
    -- rownum = 1 because there may be multiple children with this SQL_ID
    DBMS_OUTPUT.PUT_LINE(q'[Looking up SQL_ID &1]');
    SELECT sql_fulltext INTO v_sql_text FROM v$sql WHERE sql_id = '&1' AND rownum = 1;
    DBMS_OUTPUT.PUT_LINE('Found: '||SUBSTR(v_sql_text,1,80)||'...');

    -- TODO: should use PL/SQL conditional compilation here
    -- The leading space in hint_text is intentional.

    -- 12.2+
    ret := DBMS_SQLDIAG.CREATE_SQL_PATCH(sql_id=>'&1', hint_text=>q'[ &2]', name=>'SQL_PATCH_&1');

    -- 11g and 12.1
    --DBMS_SQLDIAG_INTERNAL.I_CREATE_PATCH(sql_text=>v_sql_text, hint_text=>q'[ &2]', name=>'SQL_PATCH_&1');
    DBMS_OUTPUT.PUT_LINE(q'[SQL Patch Name = SQL_PATCH_&1]');
END;
/

-- or --
SQL>DECLARE
    patch_name   VARCHAR2 (4000);
BEGIN
    patch_name :=
        DBMS_SQLDIAG.create_sql_patch (
            sql_id      => 'cafx9pnjmr7v3',
            hint_text   => q'{index_rs(@"SEL$1" "SS"@"SEL$1" ("SALES"."EMPLOYEE_ID"))}',
            name        => 'SQL_PATCH_EMPID_RANGESCAN',
            validate => true
            );
END;
/

SQL> SELECT name,
       signature,
       TO_CHAR (Created, 'DD-MON-YY') as Created_Date,
       status     
  FROM dba_sql_patches;

NAME	                        SIGNATURE	        CREATED_DATE	STATUS
--------                        ---------------         ------------    --------
SQL_PATCH_EMPID_RANGESCAN	5519277933637383967	25-MAR-25	ENABLED

SQL Patch存储在哪

和sql profile一样在sys.sqlobj$data

select
   obj.name,
   dat.comp_data,
   dat.category,
   obj.last_executed,
   txt.sql_handle,
   sql.inuse_features,
   sql.flags,
   txt.sql_text
from
   sys.sql$        sql                                        join
   sys.sql$text    txt on sql.signature = txt.signature       join
   sys.sqlobj$     obj on sql.signature = obj.signature       join
   sys.sqlobj$data dat on obj.signature = dat.signature and
                          obj.category  = dat.category
where
-- d.signature = dbms_sqltune.sqltext_to_signature(q'[select * from tq84_A t where col_2 like 'ABC%']')
   obj.name = 'TQ84_A_NO_INDEX'
;

-- or --
SELECT trim(extractValue(value(h),'.')) AS hint
FROM sys.sqlobj$data od, sys.sqlobj$ so,
table(xmlsequence(extract(xmltype(od.comp_data),'/outline_data/hint'))) h
WHERE so.name = 'TEST'
AND so.signature = od.signature
AND so.category = od.category
AND so.obj_type = od.obj_type
AND so.plan_id = od.plan_id