Oracle之SQL优化专题02-稳固SQL执行计划的方法
首先构建一个简单的测试用例来实际演示:
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执行计划的方法的更多相关文章
- Oracle之SQL优化专题01-查看SQL执行计划的方法
在我2014年总结的"SQL Tuning 基础概述"中,其实已经介绍了一些查看SQL执行计划的方法,但是不够系统和全面,所以本次SQL优化专题,就首先要系统的介绍一下查看SQL执 ...
- Oracle之SQL优化专题03-如何看懂SQL的执行计划
专题第一篇<Oracle之SQL优化专题01-查看SQL执行计划的方法>讲到了查看SQL执行计划的方法,并介绍了各种方法的应用场景,那么这一篇就主要介绍下如何看懂SQL的执行计划.毕竟如果 ...
- 浅析SqlServer简单参数化模式下对sql语句自动参数化处理以及执行计划重用
我们知道,SqlServer执行sql语句的时候,有一步是对sql进行编译以生成执行计划, 在生成执行计划之前会去缓存中查找执行计划 如果执行计划缓存中有对应的执行计划缓存,那么SqlServer就会 ...
- SQL点滴27—性能分析之执行计划
原文:SQL点滴27-性能分析之执行计划 一直想找一些关于SQL语句性能调试的权威参考,但是有参考未必就能够做好调试的工作.我深信实践中得到的经验是最珍贵的,书本知识只是一个引导.本篇来源于<I ...
- SQL Server如何查看存储过程的执行计划
有时候,我们需要查看存储过程的执行计划,那么我们有什么方式获取存储过程的历史执行计划或当前的执行计划呢? 下面总结一下获取存储过程的执行计划的方法. 1:我们可以通过下面脚本查看存储过程的执行计划,但 ...
- SQL优化的一些总结 SQL编写一般要求
SQL编写一般要求---SQL语句尽可能简单---分解联接保证高并发---同数据类型的列值比较---不在索引列做运算---禁止使用SELECT *---避免负向查询和%前缀模糊查询---保持事务(连接 ...
- 智能SQL优化工具--SQL Optimizer for SQL Server(帮助提升数据库应用程序性能,最大程度地自动优化你的SQL语句 )
SQL Optimizer for SQL Server 帮助提升数据库应用程序性能,最大程度地自动优化你的SQL语句 SQL Optimizer for SQL Server 让 SQL Serve ...
- SQL高级优化(五)之执行计划
一.explain 执行计划:在MySQL中可以通过explain关键字模拟优化器执行SQL语句,从而知道MySQL是如何处理SQL语句的. explain:MySQL执行计划的工具,查看MySQL如 ...
- SQL Sever 2008性能分析之执行计划
一直想找一些关于SQL语句性能调试的权威参考,但是有参考未必就能够做好调试 2的工作.我深信实践中得到的经验是最珍贵的,书本知识只是一个引导.本篇来源于<Inside Microsoft SQL ...
随机推荐
- xdebug的配置
第一步,让xdebug在php环境中生效 下载xdebug http://www.xdebug.org/download.php 这里会出现针对PHP各种版本的下载.找到适合你自己的版本,此处值得注意 ...
- shell脚本之通过发送带\n字符串或expect脚本实现交互输入自动化
编写shell脚本难免遇到需要交互式输入指令的步骤: 方法一: # cat action.sh #!/bin/sh read -p "enter number:" no; read ...
- iOS开发ffmpeg SDK 编译和集成
FFmpeg是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开源计算机程序.它提供了录制.转换以及流化音视频的完整解决方案.同时,FFmpeg是一套跨平台的方案,所以我们可以在iOS开发中使 ...
- 单调性 [1 + 1 / (n)]^n
def f(n): n += 0.0 s = 1 + 1 / (n) r = pow(s, n) print(n, ',', r) return r l = []for i in range(1, 1 ...
- Hystrix在项目中实践
Hystrix在项目中实践 https://mp.weixin.qq.com/s/4Fg0COnWRB3rRWfxbJt7gA
- ios -RunLoop(简单理解)
一. RunLoop简介 RunLoop字面意思是运行时,即跑圈得意思.它可以在我们需要的时候自己跑起来运行,在我们没有操作的时候就停下来休息,充分节省CPU资源,提高程序性能. 二. RunLoop ...
- 第一版STM32PCB的问题
1.5与6引脚连在一起了 2.有3个插排隔得太近,导致传感器安装不了 3.开关不匹配,画PCB时可以考虑一下实验室有哪些东西 4.可以用开关代替跳冒 5.AM3.3电路电容 6.钽电容的正负极区分 有 ...
- LeetCode 953 Verifying an Alien Dictionary 解题报告
题目要求 In an alien language, surprisingly they also use english lowercase letters, but possibly in a d ...
- Java之旅_高级教程_URL处理
摘自 :http://www.runoob.com/java/java-url-processing.html Java URL 处理 URL(Uniform Resource Locator)中文名 ...
- 洛谷P4052 [JSOI2007]文本生成器 AC自动机+dp
正解:AC自动机+dp 解题报告: 传送门! 感觉AC自动机套dp的题还挺套路的,,, 一般就先跑遍AC自动机,然后就用dp dp的状态一般都是f[i][j]:有i个字符,是ac自动机上的第j个节点, ...