1 SQL patch及适用场合
在数据库运维中,经常有需要对sql的hints进行操作,有时需要增加hint以优化语句的性能,也有的语句因为用了hint反而导致性能下降,这时就需要对语句已有的hint进行删除。生产环境中,对SQL语句的调整并不总是可行的,这时就需要在不更改SQL的情况下对它的hint进行操作,SQL patch就是一个可以选择的工具,sqlpatch对标准化的SQL语句生效(这意味这空格和非字面值的大小写不影响sqlpatch的选择),直接对语句的hint进行操作,这个工具在dbms_sqldiag包中,可以由sqldiag任务生成,也可以手动生成,手动生成在Oracle Database 12c Release 2之后成了公开特性,操作起来更加简易,之前的版本中,手动创建sqlpatch是内部特性,没有公开。
2 SQL patch的基本操作
2.1 CREATE_SQL_PATCH函数–手动创建SQL patch
DBMS_SQLDIAG.CREATE_SQL_PATCH ( sql_text IN CLOB, hint_text IN CLOB, name IN VARCHAR2 := NULL, description IN VARCHAR2 := NULL, category IN VARCHAR2 := NULL, validate IN BOOLEAN := TRUE) RETURN VARCHAR2;
这个函数还有另一种形式,参数中sql_text可以替换为sql_id(参数类型是varchar2),返回SQL patch名。
2.2 ALTER_SQL_PATCH存储过程—更改SQL patch属性
DBMS_SQLDIAG.ALTER_SQL_PATCH ( name IN VARCHAR2, attribute_name IN VARCHAR2, attribute_value IN VARCHAR2);
这个存储过程可以更改SQL patch的下列属性
- STATUS -> can be set to ENABLED or DISABLED
- NAME -> can be reset to a valid name (must be a valid Oracle identifier and must be unique).
- DESCRIPTION -> can be set to any string of size no more than 500
- CATEGORY -> can be reset to a valid category name (must be valid Oracle identifier and must be unique when combined with normalized SQL text)
2.3 DROP_SQL_PATCH存储过程—删除SQL patch
DBMS_SQLDIAG.DROP_SQL_PATCH ( name IN VARCHAR2, ignore IN BOOLEAN := FALSE);
这个存储过程接受两个参数,name是要删除的SQL patch的名称,ignore参数指定是否忽略因对象不存在导致的错误。
3 取消SQL语句现有的hints
3.1 创建SQL patch
SELECT /*+ full(c) */ * FROM CUSTOMER_ORDERS c WHEREc.ORDER_DATE>sysdate-3
Plan hash value: 86929056
-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 1893 (100)| |
|* 1 | TABLE ACCESS FULL| CUSTOMER_ORDERS | 484 | 21780 | 1893 (2)| 00:00:01 |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("C"."ORDER_DATE">SYSDATE@!-3)
上面的语句使用了不恰当的hint导致了全表扫描,可以使用sqlpatch使语句中的hint失效,创建hint的语句如下
SQL> declare patch_name varchar2(30);
begin
patch_name := dbms_sqldiag.create_sql_patch(
sql_id=>'2j68tj85209wp',
hint_text=>' IGNORE_OPTIM_EMBEDDED_HINTS');
end; 2 3 4 5 6
7 /
PL/SQL procedure successfully completed.
3.2 检查SQL patch是否生效
这是再看这条语句的执行计划
SQL_ID 2j68tj85209wp, child number 0
-------------------------------------
SELECT /*+ full(c) */ * FROM CUSTOMER_ORDERS c WHERE
c.ORDER_DATE>sysdate-3
Plan hash value: 1825996753
-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 462 (100)| |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| CUSTOMER_ORDERS | 484 | 21780 | 462 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | IDX_CUST_ORDER_DATE | 484 | | 4 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("C"."ORDER_DATE">SYSDATE@!-3)
Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1 (U - Unused (1))
---------------------------------------------------------------------------
1 - SEL$1 / "C"@"SEL$1"
U - full(c) / rejected by IGNORE_OPTIM_EMBEDDED_HINTS
Note
-----
- SQL patch "SYS_SQLPTCH_019904398ed20000" used for this statement
创建的SQL patch也也可以用于标准化后相同的其它SQL语句
SQL_ID 0hfaz2uztd3w5, child number 0
-------------------------------------
SELECT /*+ full(c) */ * from customer_orders c WHERE
c.ORDER_DATE>sysdate-3
Plan hash value: 1825996753
-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 462 (100)| |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| CUSTOMER_ORDERS | 484 | 21780 | 462 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | IDX_CUST_ORDER_DATE | 484 | | 4 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("C"."ORDER_DATE">SYSDATE@!-3)
Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1 (U - Unused (1))
1 - SEL$1 / "C"@"SEL$1"
U - full(c) / rejected by IGNORE_OPTIM_EMBEDDED_HINTS
Note
-----
- SQL patch "SYS_SQLPTCH_019904398ed20000" used for this statement
这条语句和上一条语句的sql id并不相同,原因是它们有几处大小写不同,但是创建的SQL patch同样可以用于这条语句,两条语句的执行计划hash值也相同。
3.3 查看创建的SQL patch
可以从DBA_SQL_PATCHES视图中查询到刚才创建的SQL patch
NAME SQL_TEXT STATUS
-------------------------------- ---------------------------------------------------------------- --------
SYS_SQLPTCH_019904398ed20000 SELECT /*+ full(c) */ * FROM CUSTOMER_ORDERS c WHERE c.ORDER_DAT ENABLED
E>sysdate-3
虽然在创建SQL patch中使用的参数是sql id,这个视图保存的信息却是sql_text,这个文本是起初的sql语句,而不是标准化之后的文本。
4 为SQL语句添加hint
4.1 获得系统产生的查询块(query block)名称
在为语句添加hint时,往往需要获得hint要作用的查询块名称,这个名称可以在显示语句的执行计划时,用格式alias来获得:
SQL> select * from table(dbms_xplan.display_cursor('d1v2wt8n1tfrd',FORMAT=>'basic +alias'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
SELECT * FROM test.CUSTOMER_ORDERS c WHERE c.CUSTOMER_ID = 492
Plan hash value: 86929056
---------------------------------------------
| Id | Operation | Name |
---------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | TABLE ACCESS FULL| CUSTOMER_ORDERS |
---------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
1 - SEL$1 / "C"@"SEL$1"
上面的语句只有一个查询块,SEL是它的操作,这里是select,还有其它的操作,如CRI,CREATE INDEX statements,DEL$ DELETE statements,INS$ INSERT statements,后面的数字是操作的id,按照SQL书写的顺序从左向右依次递增。如果优化器对语句进行了转换,情况又有所不同,例如下面的语句
SQL> select * from table(dbms_xplan.display_cursor('g1mu28k4n5wmt',FORMAT=>'basic +alias'));
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
SELECT * FROM CUSTOMER_ORDERS c WHERE c.CUSTOMER_ID IN (SELECT
customer_id FROM CUSTOMER_ADDRESSES c2 WHERE city='Tokyo') and
c.ORDER_DATE > sysdate -5
Plan hash value: 2850931541
--------------------------------------------------------------------
| Id | Operation | Name |
--------------------------------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | HASH JOIN RIGHT SEMI | |
| 2 | TABLE ACCESS FULL | CUSTOMER_ADDRESSES |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| CUSTOMER_ORDERS |
| 4 | INDEX RANGE SCAN | IDX_CUST_ORDER_DATE |
--------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
1 - SEL$5DA710D3
2 - SEL$5DA710D3 / "C2"@"SEL$2"
3 - SEL$5DA710D3 / "C"@"SEL$1"
4 - SEL$5DA710D3 / "C"@"SEL$1"
Oracle 优化器对语句进行了转换,这个查询转换也产生了一个查询块,这个查询块在解析的SQL语句中并不存在,因此不能像语句中的查询块那样命名,查询优化器为它产生了一个8位的hash值,如上图中的5DA710D3。在hints中,这个查询块名称和SQL语句中的查询块名称(如SEL$2,SEL$1)都可以用,建议使用SQL语句中原有的查询快名称(SEL$1,SEL$2),因为如果转换不发生或者发生的转换不同,这个产生的查询块的名称就不存在了。
4.2 给语句添加hint
SQL> declare patch_name varchar2(30);
begin
patch_name := dbms_sqldiag.create_sql_patch(
sql_id=>'g1mu28k4n5wmt',
hint_text=>' FULL(@"SEL$1" "C"@"SEL$1")');
end; 2 3 4 5 6
7 /
PL/SQL procedure successfully completed.
执行语句后再查看语句的执行计划如下
SQL> select * from table(dbms_xplan.display_cursor('g1mu28k4n5wmt'));
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID g1mu28k4n5wmt, child number 0
-------------------------------------
SELECT * FROM CUSTOMER_ORDERS c WHERE c.CUSTOMER_ID IN (SELECT
customer_id FROM CUSTOMER_ADDRESSES c2 WHERE city='Tokyo') and
c.ORDER_DATE > sysdate -5
Plan hash value: 3000344517
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 1907 (100)| |
|* 1 | HASH JOIN RIGHT SEMI| | 471 | 26847 | 1907 (2)| 00:00:01 |
|* 2 | TABLE ACCESS FULL | CUSTOMER_ADDRESSES | 447 | 5364 | 14 (0)| 00:00:01 |
|* 3 | TABLE ACCESS FULL | CUSTOMER_ORDERS | 484 | 21780 | 1893 (2)| 00:00:01 |
-------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("C"."CUSTOMER_ID"="CUSTOMER_ID")
2 - filter("CITY"='Tokyo')
3 - filter("C"."ORDER_DATE">SYSDATE@!-5)
Note
-----
- SQL patch "SYS_SQLPTCH_01990d591e790000" used for this statement
SQL patch生效,CUSTOMER_ORDERS的访问方式变为全表扫描。
4.3 直接替换原来的hint
针对第三节的SQL语句,创建SQL patch,将原来的全表扫描替换为索引扫描
SQL> declare patch_name varchar2(30);
begin
patch_name := dbms_sqldiag.create_sql_patch(
sql_id=>'892xgtkd5jx55',
hint_text=>'INDEX(@"SEL$1" "C"@"SEL$1")');
end; 2 3 4 5 6
7 /
PL/SQL procedure successfully completed.
语句的执行计划变为
SQL> select * from table(dbms_xplan.display_cursor('892xgtkd5jx55'));
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 892xgtkd5jx55, child number 0
-------------------------------------
SELECT /*+ full(c) */ * FROM CUSTOMER_ORDERS c WHERE
c.ORDER_DATE>sysdate-3
Plan hash value: 1825996753
-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 462 (100)| |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| CUSTOMER_ORDERS | 484 | 21780 | 462 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | IDX_CUST_ORDER_DATE | 484 | | 4 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("C"."ORDER_DATE">SYSDATE@!-3)
Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1 (U - Unused (1))
---------------------------------------------------------------------------
1 - SEL$1 / "C"@"SEL$1"
U - full(c) / hint overridden by another in parent query block
Note
-----
- SQL patch "SYS_SQLPTCH_01990d6992dd0001" used for this statement
对CUSTOMER_ORDERS表的访问方式变为索引范围扫描,hint报告里显示使用了SQL patch,full©未使用,原因时被父查询块里的其它hint覆盖。如果hint作用的查询块不同,则SQL patch的hint同语句原来的hint一起作用,这里不再演示。
5 小结
从database 12.2开始,SQL patch已经变成公开特性,在Oracle 官网里可以查到,这为DBA提供了另一种调整语句hint的方法,SQL patch里hint的优先级高于语句里hint的优先级。




