首先构建一个简单的测试用例来实际演示:

create table emp as select * from scott.emp;
create table dept as select * from scott.dept;
create index idx_emp_empno on emp(empno);
create index idx_dept_deptno on dept(deptno);

测试过程中查看真实执行计划的方法:

set lines 1000 pages 1000
alter session set statistics_level = ALL;
Execute SQL;
select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));

正常的SQL执行,执行计划会走相应的索引:

--good SQL: 39dv3d8jkzyuw
select a.empno, a.ename, b.dname, a.job, a.sal from emp a, dept b where a.deptno = b.deptno and empno = 7788; --good xplan: 1725450077
SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last')); PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 39dv3d8jkzyuw, child number 0
-------------------------------------
select a.empno, a.ename, b.dname, a.job, a.sal from emp a, dept b where
a.deptno = b.deptno and empno = 7788 Plan hash value: 1725450077 --------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
--------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 6 | 2 |
| 1 | NESTED LOOPS | | 1 | 1 | 1 |00:00:00.01 | 6 | 2 |
| 2 | NESTED LOOPS | | 1 | 1 | 1 |00:00:00.01 | 5 | 2 |
| 3 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 1 | 1 |00:00:00.01 | 3 | 1 |
|* 4 | INDEX RANGE SCAN | IDX_EMP_EMPNO | 1 | 1 | 1 |00:00:00.01 | 2 | 1 |
|* 5 | INDEX RANGE SCAN | IDX_DEPT_DEPTNO | 1 | 1 | 1 |00:00:00.01 | 2 | 1 |
| 6 | TABLE ACCESS BY INDEX ROWID | DEPT | 1 | 1 | 1 |00:00:00.01 | 1 | 0 |
-------------------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id):
--------------------------------------------------- 4 - access("EMPNO"=7788)
5 - access("A"."DEPTNO"="B"."DEPTNO")

糟糕的SQL执行,执行计划走全表扫描(这里实验直接利用使用hint强制不走索引来模拟这种情况):

--bad SQL: dqd10y7wqrg7f
select /*+ no_index(a idx_emp_empno) no_index(b idx_dept_deptno)*/a.empno, a.ename, b.dname, a.job, a.sal from emp a, dept b where a.deptno = b.deptno and empno = 7788; --bad xplan: 1123238657
SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last')); PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID dqd10y7wqrg7f, child number 1
-------------------------------------
select /*+ no_index(a idx_emp_empno) no_index(b
idx_dept_deptno)*/a.empno, a.ename, b.dname, a.job, a.sal from emp a,
dept b where a.deptno = b.deptno and empno = 7788 Plan hash value: 1123238657 ----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 5 | | | |
|* 1 | HASH JOIN | | 1 | 1 | 1 |00:00:00.01 | 5 | 1214K| 1214K| 377K (0)|
|* 2 | TABLE ACCESS FULL| EMP | 1 | 1 | 1 |00:00:00.01 | 2 | | | |
| 3 | TABLE ACCESS FULL| DEPT | 1 | 4 | 4 |00:00:00.01 | 3 | | | |
---------------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id):
--------------------------------------------------- 1 - access("A"."DEPTNO"="B"."DEPTNO")
2 - filter("EMPNO"=7788)

假设此时这些糟糕的SQL就是业务实际的SQL,且对应开发人员无法更改SQL文本(这里就是指无法去掉不走索引的hint),那么现在如何能将这些糟糕的SQL绑定成走索引的执行计划呢?

糟糕的SQL清单:

select /*+ no_index(a idx_emp_empno) no_index(b idx_dept_deptno)*/a.empno, a.ename, b.dname, a.job, a.sal from emp a, dept b where a.deptno = b.deptno and empno = 7788;

select /*+ no_index(a idx_emp_empno) no_index(b idx_dept_deptno)*/a.empno, a.ename, b.dname, a.job, a.sal from emp a, dept b where a.deptno = b.deptno and empno = 7900;

如何让其走索引?目前Oracle常见的2种稳固执行计划的方式:

1.SQL Profile稳固执行计划

适用于Oracle 10g及以上版本。
利用MOS文档215187.1提供的系列脚本中的coe_xfr_sql_profile.sql来稳固执行计划,只需要输入要调整SQL的SQL_ID和好的执行计划的plan_hash_value即可,脚本内容可参考:
- [使用COE脚本绑定SQL Profile](https://www.cnblogs.com/jyzhao/p/9256293.html)

在本次演示实验中,就是将sql_id='dqd10y7wqrg7f'的SQL绑定好的plan_hash_value=1725450077,具体使用过程如下:

SQL> @coe_xfr_sql_profile.sql

Parameter 1:
SQL_ID (required) Enter value for 1: dqd10y7wqrg7f PLAN_HASH_VALUE AVG_ET_SECS
--------------- -----------
1123238657 .095 Parameter 2:
PLAN_HASH_VALUE (required) Enter value for 2: 1725450077 Values passed to coe_xfr_sql_profile:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SQL_ID : "dqd10y7wqrg7f"
PLAN_HASH_VALUE: "1725450077"
...
Execute coe_xfr_sql_profile_dqd10y7wqrg7f_1725450077.sql
on TARGET system in order to create a custom SQL Profile
with plan 1725450077 linked to adjusted sql_text. COE_XFR_SQL_PROFILE completed.

然后按照提示执行生成的coe_xfr_sql_profile_dqd10y7wqrg7f_1725450077.sql脚本即可;

需要特别注意的是:可以根据实际情况是否需要修改这个脚本中的force_match的值为true。

本次的例子,就是没有使用到绑定变量,而需求是不仅让empno = 7788的条件走索引,还要让其他输入值,比如empno = 7900也同样走索引,那就需要修改这个force_match的值为true。稳固执行计划的效果如下:

SQL> select /*+ no_index(a idx_emp_empno) no_index(b idx_dept_deptno)*/a.empno, a.ename, b.dname, a.job, a.sal from emp a, dept b where a.deptno = b.deptno and empno = 7788;

     EMPNO ENAME      DNAME          JOB              SAL
---------- ---------- -------------- --------- ----------
7788 SCOTT RESEARCH ANALYST 3000 SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last')); PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID dqd10y7wqrg7f, child number 0
-------------------------------------
select /*+ no_index(a idx_emp_empno) no_index(b
idx_dept_deptno)*/a.empno, a.ename, b.dname, a.job, a.sal from emp a,
dept b where a.deptno = b.deptno and empno = 7788 Plan hash value: 1725450077 -----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 6 |
| 1 | NESTED LOOPS | | 1 | 1 | 1 |00:00:00.01 | 6 |
| 2 | NESTED LOOPS | | 1 | 1 | 1 |00:00:00.01 | 5 |
| 3 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 1 | 1 |00:00:00.01 | 3 |
|* 4 | INDEX RANGE SCAN | IDX_EMP_EMPNO | 1 | 1 | 1 |00:00:00.01 | 2 |
|* 5 | INDEX RANGE SCAN | IDX_DEPT_DEPTNO | 1 | 1 | 1 |00:00:00.01 | 2 |
| 6 | TABLE ACCESS BY INDEX ROWID | DEPT | 1 | 1 | 1 |00:00:00.01 | 1 |
----------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id):
--------------------------------------------------- 4 - access("EMPNO"=7788)
5 - access("A"."DEPTNO"="B"."DEPTNO") Note
-----
- SQL profile coe_dqd10y7wqrg7f_1725450077 used for this statement

常用操作:

1)查询sql_profile

可以通过查询dba_sql_profiles来确认数据库中的sql_profile:

select * from dba_sql_profiles;

2)删除sql_profile

如果有一天不再需要这个sql_profile来稳固执行计划,可以这样删除sql_profile:

exec dbms_sqltune.drop_sql_profile('name');

exec dbms_sqltune.drop_sql_profile('coe_dqd10y7wqrg7f_1725450077');

3)清除SQL执行计划

还可以清除共享池中指定SQL的执行计划:

exec sys.dbms_shared_pool.purge('address,hash_value','c');

SQL> select sql_id, address, hash_value, plan_hash_value, sql_profile from v$sql where sql_id = 'dqd10y7wqrg7f';

SQL_ID        ADDRESS          HASH_VALUE PLAN_HASH_VALUE SQL_PROFILE
------------- ---------------- ---------- --------------- ----------------------------------------------------------------
dqd10y7wqrg7f 0000000076B909F8 4184587502 1123238657
dqd10y7wqrg7f 0000000076B909F8 4184587502 1123238657
dqd10y7wqrg7f 0000000076B909F8 4184587502 1725450077 coe_dqd10y7wqrg7f_1725450077 SQL> exec sys.dbms_shared_pool.purge('0000000076B909F8,4184587502','c'); PL/SQL procedure successfully completed. SQL> select sql_id, address, hash_value, plan_hash_value, sql_profile from v$sql where sql_id = 'dqd10y7wqrg7f'; no rows selected

2.SPM稳固执行计划

适用于Oracle 11g及以上版本。
删除掉之前的sql_profile,尝试使用SPM来稳固执行计划,实际上,手工生成sql_plan_baseline的方式要更加灵活,但我实际用的比较少。
查看sql_plan_baselines:

select * from dba_sql_plan_baselines;
select sql_handle, plan_name, origin, enabled, accepted, fixed from dba_sql_plan_baselines;

SPM稳固执行计划方法:

var temp number
--1.bad: sql_id & plan_hash_value
exec :temp := dbms_spm.load_plans_from_cursor_cache(sql_id => '', plan_hash_value => );
--2.good: sql_id & plan_hash_value & sql_handle
exec :temp := dbms_spm.load_plans_from_cursor_cache(sql_id => '', plan_hash_value => , sql_handle => );
--3.drop bad plan_name
exec :temp := dbms_spm.drop_sql_plan_baseline(sql_handle => '', plan_name => '');

用上面的例子具体说明:

--1.bad: sql_id & plan_hash_value
SQL> var temp number
SQL> exec :temp := dbms_spm.load_plans_from_cursor_cache(sql_id => 'dqd10y7wqrg7f', plan_hash_value => 1123238657);
SQL> select sql_handle, plan_name, origin, enabled, accepted, fixed from dba_sql_plan_baselines; SQL_HANDLE PLAN_NAME ORIGIN ENA ACC FIX
------------------------------ ------------------------------ -------------- --- --- ---
SQL_9c3626a309e5e8bd SQL_PLAN_9sdj6nc4ybu5x96fd8705 MANUAL-LOAD YES YES NO --2.good: sql_id & plan_hash_value & sql_handle(上面查到的)
SQL> exec :temp := dbms_spm.load_plans_from_cursor_cache(sql_id => '39dv3d8jkzyuw', plan_hash_value =>1725450077, sql_handle => 'SQL_9c3626a309e5e8bd'); PL/SQL procedure successfully completed. SQL> select sql_handle, plan_name, origin, enabled, accepted, fixed from dba_sql_plan_baselines; SQL_HANDLE PLAN_NAME ORIGIN ENA ACC FIX
------------------------------ ------------------------------ -------------- --- --- ---
SQL_9c3626a309e5e8bd SQL_PLAN_9sdj6nc4ybu5x2b78d17a MANUAL-LOAD YES YES NO
SQL_9c3626a309e5e8bd SQL_PLAN_9sdj6nc4ybu5x96fd8705 MANUAL-LOAD YES YES NO --3.drop bad plan_name
SQL> exec :temp := dbms_spm.drop_sql_plan_baseline(sql_handle => 'SQL_9c3626a309e5e8bd', plan_name => 'SQL_PLAN_9sdj6nc4ybu5x96fd8705'); PL/SQL procedure successfully completed. SQL> select sql_handle, plan_name, origin, enabled, accepted, fixed from dba_sql_plan_baselines; SQL_HANDLE PLAN_NAME ORIGIN ENA ACC FIX
------------------------------ ------------------------------ -------------- --- --- ---
SQL_9c3626a309e5e8bd SQL_PLAN_9sdj6nc4ybu5x2b78d17a MANUAL-LOAD YES YES NO

验证稳固执行计划的效果:

SQL> select /*+ no_index(a idx_emp_empno) no_index(b idx_dept_deptno)*/a.empno, a.ename, b.dname, a.job, a.sal from emp a, dept b where a.deptno = b.deptno and empno = 7788;

     EMPNO ENAME      DNAME          JOB              SAL
---------- ---------- -------------- --------- ----------
7788 SCOTT RESEARCH ANALYST 3000 SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last')); PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID dqd10y7wqrg7f, child number 1
-------------------------------------
select /*+ no_index(a idx_emp_empno) no_index(b
idx_dept_deptno)*/a.empno, a.ename, b.dname, a.job, a.sal from emp a,
dept b where a.deptno = b.deptno and empno = 7788 Plan hash value: 1725450077 -----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 6 |
| 1 | NESTED LOOPS | | 1 | 1 | 1 |00:00:00.01 | 6 |
| 2 | NESTED LOOPS | | 1 | 1 | 1 |00:00:00.01 | 5 |
| 3 | TABLE ACCESS BY INDEX ROWID| EMP | 1 | 1 | 1 |00:00:00.01 | 3 |
|* 4 | INDEX RANGE SCAN | IDX_EMP_EMPNO | 1 | 1 | 1 |00:00:00.01 | 2 |
|* 5 | INDEX RANGE SCAN | IDX_DEPT_DEPTNO | 1 | 1 | 1 |00:00:00.01 | 2 |
| 6 | TABLE ACCESS BY INDEX ROWID | DEPT | 1 | 1 | 1 |00:00:00.01 | 1 |
----------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id):
--------------------------------------------------- 4 - access("EMPNO"=7788)
5 - access("A"."DEPTNO"="B"."DEPTNO") Note
-----
- SQL plan baseline SQL_PLAN_9sdj6nc4ybu5x2b78d17a used for this statement

可以看到SPM已经起作用了。但如果谓词条件换成7900,就会不起作用。我没有找到SPM中类似像sql_profile中force_match的参数,日常工作中也是使用sql_profile稳固执行计划多一些。

另外注意dba_sql_plan_baselines中记录的执行计划对应的ACCEPTED和ENABLE的值都为YES,才可能会被SQL使用。

常用操作:

select sql_handle, plan_name, origin, enabled, accepted, fixed from dba_sql_plan_baselines;

1)将ENABLE的值设为"YES" or "NO"

var temp number
exec :temp := dbms_spm.alter_sql_plan_baseline(sql_handle => 'SQL_9c3626a309e5e8bd', plan_name => 'SQL_PLAN_9sdj6nc4ybu5x96fd8705', attribute_name => 'ENABLED', attribute_value => 'YES'); var temp number
exec :temp := dbms_spm.alter_sql_plan_baseline(sql_handle => 'SQL_9c3626a309e5e8bd', plan_name => 'SQL_PLAN_9sdj6nc4ybu5x96fd8705', attribute_name => 'ENABLED', attribute_value => 'NO');

2)将ACCEPTED值设为"YES"

var temp clob
exec :temp := dbms_spm.evolve_sql_plan_baseline(sql_handle => 'SQL_9c3626a309e5e8bd', plan_name => 'SQL_PLAN_9sdj6nc4ybu5x96fd8705', verify => 'NO', commit => 'YES');

注:我这里测试(在11.2.0.4环境下)发现ACCEPTED值设为"YES"后,无法再设置成"NO",而ENABLED的值可以自由设置为"YES" or "NO"。

Oracle之SQL优化专题02-稳固SQL执行计划的方法的更多相关文章

  1. Oracle之SQL优化专题01-查看SQL执行计划的方法

    在我2014年总结的"SQL Tuning 基础概述"中,其实已经介绍了一些查看SQL执行计划的方法,但是不够系统和全面,所以本次SQL优化专题,就首先要系统的介绍一下查看SQL执 ...

  2. Oracle之SQL优化专题03-如何看懂SQL的执行计划

    专题第一篇<Oracle之SQL优化专题01-查看SQL执行计划的方法>讲到了查看SQL执行计划的方法,并介绍了各种方法的应用场景,那么这一篇就主要介绍下如何看懂SQL的执行计划.毕竟如果 ...

  3. 浅析SqlServer简单参数化模式下对sql语句自动参数化处理以及执行计划重用

    我们知道,SqlServer执行sql语句的时候,有一步是对sql进行编译以生成执行计划, 在生成执行计划之前会去缓存中查找执行计划 如果执行计划缓存中有对应的执行计划缓存,那么SqlServer就会 ...

  4. SQL点滴27—性能分析之执行计划

    原文:SQL点滴27-性能分析之执行计划 一直想找一些关于SQL语句性能调试的权威参考,但是有参考未必就能够做好调试的工作.我深信实践中得到的经验是最珍贵的,书本知识只是一个引导.本篇来源于<I ...

  5. SQL Server如何查看存储过程的执行计划

    有时候,我们需要查看存储过程的执行计划,那么我们有什么方式获取存储过程的历史执行计划或当前的执行计划呢? 下面总结一下获取存储过程的执行计划的方法. 1:我们可以通过下面脚本查看存储过程的执行计划,但 ...

  6. SQL优化的一些总结 SQL编写一般要求

    SQL编写一般要求---SQL语句尽可能简单---分解联接保证高并发---同数据类型的列值比较---不在索引列做运算---禁止使用SELECT *---避免负向查询和%前缀模糊查询---保持事务(连接 ...

  7. 智能SQL优化工具--SQL Optimizer for SQL Server(帮助提升数据库应用程序性能,最大程度地自动优化你的SQL语句 )

    SQL Optimizer for SQL Server 帮助提升数据库应用程序性能,最大程度地自动优化你的SQL语句 SQL Optimizer for SQL Server 让 SQL Serve ...

  8. SQL高级优化(五)之执行计划

    一.explain 执行计划:在MySQL中可以通过explain关键字模拟优化器执行SQL语句,从而知道MySQL是如何处理SQL语句的. explain:MySQL执行计划的工具,查看MySQL如 ...

  9. SQL Sever 2008性能分析之执行计划

    一直想找一些关于SQL语句性能调试的权威参考,但是有参考未必就能够做好调试 2的工作.我深信实践中得到的经验是最珍贵的,书本知识只是一个引导.本篇来源于<Inside Microsoft SQL ...

随机推荐

  1. {Django基础九之中间件} 一 前戏 二 中间件介绍 三 自定义中间件 四 中间件的执行流程 五 中间件版登陆认证

    Django基础九之中间件 本节目录 一 前戏 二 中间件介绍 三 自定义中间件 四 中间件的执行流程 五 中间件版登陆认证 六 xxx 七 xxx 八 xxx 一 前戏 我们在前面的课程中已经学会了 ...

  2. Vue.js最佳实践

    Vue.js最佳实践 第一招:化繁为简的Watchers 场景还原: created(){ this.fetchPostList() }, watch: { searchInputValue(){ t ...

  3. Django:视图views(三)

    写一下Cookie.重定向.Session Cookie 测试代码,承接前面的代码: 路由: booktest/urls.py urlpatterns = [ url('^$',views.index ...

  4. ASP_NET实现界面无刷新的DropdownList两级联动效果

    所谓DropdownList联动,也就是在选一个DropdownList的时候使另外一个DropdownList的内容更新(如选省份时显示所属城市),按常规的方法那就是在第一个DropdownList ...

  5. Runloop的再学习之浅析(一)

    一,认识RunLoop 我的理解: 1. 在编程的世界里,万物皆对象.所以RunLoop 实际上也是一个对象,这个对象管理了其需要 处理的事件和消息,并提供了一个入口函数来执行上面 Event Loo ...

  6. [dpdk] dpdk启动几个线程

    看别人的代码搞得有点晕,突然有点不确定,再确认一次. 使用 helloworld程序测试一下. /root/dpdk-16.07/examples/helloworld 一:  只启动一个核心. [r ...

  7. 使用Python + Selenium破解滑块验证码

    在前面一篇博客<使用 Python + Selenium 打造浏览器爬虫>中,我介绍了 Selenium 的基本用法和爬虫开发过程中经常使用的一些小技巧,利用这些写出一个浏览器爬虫已经完全 ...

  8. inet_addr()和inet_ntoa()使用注意

    inet_addr():无法处理255.255.255.255,认为该ip为非法,返回-1 inet_ntoa():转换后地址存储在静态变量中,连续两次调用,第二次会覆盖第一次的值. 建议使用inet ...

  9. for in 循环

    for  in循环可以循环遍历数组  关键也可以循环遍历对象!而一般的for循环只能循环遍历数组, 当循环遍历对象时key值代表键值对的键,obj[key]则是对应键的值: 当循环遍历数组时,数组不是 ...

  10. 洛谷P4562 [JXOI2018]游戏 数论

    正解:数论 解题报告: 传送门! 首先考虑怎么样的数可能出现在t(i)那个位置上?显然是[l,r]中所有无法被表示出来的数(就约数不在[l,r]内的数嘛QwQ 所以可以先把这些数筛出来 具体怎么筛的话 ...