Oracle绑定变量窥探

随着具体输入值的不同,SQL的where条件的可选择率(Selectivity)和结果集的行数(Cardinality)可能会随之发生变化,而Selectivity和Cardinality的值会直接影响CBO对于相关执行步骤成本值的估算,进而影响CBO对SQL执行计划的选择。这就意味着随着具体输入值的不同,目标SQL执行计划可能会发生变化。

对于不使用绑定变量的SQL而言,具体输入值一量发生了变化,目标SQL的SQL文本就会随之发生变化,这样Oracle就能很容易地计算出对应Selectivity和Cardinality的值,进而据此来选择执行计划。但对于使用绑定变量的SQL而言,情况就完全不一样了,因为现在无论对应绑定变量的具体输入值是什么,目标SQL的SQL文本都是一模一样的,这种情况下Oracle应该如何来决定目标SQL的执行计划呢?

对于使用了绑定变量的SQL而言,Oracle可以选择如下两种方法来决定其执行计划:

  • 使用绑定变量窥探
  • 如果不使用绑定变量窥探,则对于那些可选择率可能会随着具体输入值的不同而不同的谓词条件使用默认的可选择率(例如5%)。

绑定变量窥探(Bind Peeking)是在Oracle 9i中引入的,是否启用绑定变量窥探受隐含参数_OPTIM_PEEK_USER_BINDS的控制,_OPTIM_PEEK_USER_BINDS的默认值是TRUE,表示在Oracle 9i及其后续的版本中,绑定变量窥探在默认情况下就已经被启用了。

当绑定变量窥探被启用后,每当Oracle以硬解析的方式解析使用了绑定变量的目标SQL时,Oracle都会实际窥探(Peeking)一下对应绑定变量的具体输入值,并以这些具体输入值为标准,来决定这些使用了绑定变量的目标SQL的where条件的Selectivity和Cardinality的值,并据此来选择该SQL的执行计划。这个“窥探(Peeking)”的动作只有在硬解析的时候才会执行,当使用了绑定变量的目标SQL再次执行时(此时对应的是软解析/软软解析),即便此时对应绑定变量的具体输入值和之前硬解析时对应的值不同,Oracle也会沿用之前硬解析时所产生的解析树和执行计划,而不再重复执行上述“窥探”的动作。

绑定变量窥探的好处是显而易见的,因为有了绑定变量窥探,Oracle在计算目标SQL的where条件的Selectivity和Cardinality的值时,就可以避免使用默认的可选择率,这样就有更大的可能性得到该SQL准确的执行计划。同样,绑定变量窥探的坏处也是显而易见的,对于那些执行计划可能会随着对应绑定变量具体输入值的不同而变化的目标SQL而言,一旦启用了绑定变量窥探,其执行计划就会被固定下来,到于这个固定下来的执行计划到底是什么,则完全倚赖于该SQL在硬解析时传入的对应绑定变量的具体值。这意味着一量启用了绑定变量窥探,目标SQL在后续执行时就会沿用之前硬解析所产生的解析树和执行计划,即使这种沿用并不适合于当前的情形。

绑定变量窥探这种不管后续传入的绑定变量的具体输入值是什么而一直沿用之前硬解析时所产生的解析权和执行计划的特性一直饱受诟病(这种状况一直到Oracle 11g中引入自适应游标共享后才有所缓解),因为它可能使CBO在某些情况下(对应绑定变量的某些具体输入值)所选择的执行计划并不是目标SQL在当前情形下是最优执行计划,而且它可能会带来目标SQL执行计划的突然改变,进而直接影响应用系统的性能。

比如某个SQL的执行计划随着绑定变量具体输入值的不同会对应两个执行计划,一个是走对索引的索引范围扫描,另一个是走对索引的索引快速全扫描。正常情况下,对绝大多数绑定变量输入值,执行计划都应该走索引范围扫描,极少数情况下会走索引快速全扫描。但假如有一开该SQL对应的Shared Cursor被age out出Shared Pool了,那么当该SQL再次执行时Oracle就得硬解析。不幸的是如果这次硬解析时传入的绑定变量输入值恰好是走索引快速全扫描所对应的极少数的情形,那么后续的SQL走会走这个执行计划,这种情况下该SQL的执行效率就很可能比之前慢一个甚至多个数量级。表现在在应用系统上就是突然有一天发现某个应用跑不动了,而之前一直是好好的。

下面看一个绑定变量窥探的实例:

创建测试表T1及索引并收集统计信息

[email protected]>create table t1 as select * from dba_objects;

Table created.

[email protected]>create index idx_t1 on t1(object_id);

Index created.

[email protected]>select count(*) from t1;

  COUNT(*)
----------
     72005

[email protected]>select count(distinct(object_id)) from t1;

COUNT(DISTINCT(OBJECT_ID))
--------------------------
		     72005

[email protected]>exec dbms_stats.gather_table_stats(ownname=>USER,tabname=>‘T1‘,estimate_percent=>100,cascade=>true,method_opt=>‘for all columns size 1‘,no_invalidate=>false);

PL/SQL procedure successfully completed.

执行如下两个sql并查看Oracle对SQL的解析情况

[email protected]>select count(*) from t1 where object_id between 999 and 1000;

  COUNT(*)
----------
	 2

[email protected]>select count(*) from t1 where object_id between 999 and 60000;

  COUNT(*)
----------
     58180

[email protected]>col sql_text for a80
[email protected]>select sql_text,sql_id,version_count,executions from v$sqlarea where sql_text like ‘select count(*) from t1 %‘;

SQL_TEXT									 SQL_ID 				 VERSION_COUNT EXECUTIONS
-------------------------------------------------------------------------------- --------------------------------------- ------------- ----------
select count(*) from t1 where object_id between 999 and 1000			 5gu397922cuqd					     1		1
select count(*) from t1 where object_id between 999 and 60000			 b8xxw70vja3tn					     1		1

从查询结果可以看出,Oracle在执行上述SQL时都使用了硬解析。Oracle分别为上述两个SQL各自生成了一个Parent Cursor和一个Child Cursor。

再查看执行计划:

从执行计划可以看出between 999 and 1000条件的SQL走的是索引范围扫描,而between 999 and 60000走的执行计划是索引快速全扫描。

现在我们将全面的两个SQL改造成使用绑定变量的等价形式。定义两个绑定变量x和y,并分别给它们赋值999和1000。

[email protected]>var x number;
[email protected]>var y number;
[email protected]>exec :x := 999;

PL/SQL procedure successfully completed.

[email protected]>exec :y := 1000;

PL/SQL procedure successfully completed.

显然,此时用绑定变量x和y的改写形式“between :x and :y”与原来的“between 999 and 1000”是等价的。而且只要将y重新赋值为60000,则又和“between 999 and 60000”等价了。

现在x和y的值分别为999和100,执行改写后的sql

[email protected]>select count(*) from t1 where object_id between :x and :y;

  COUNT(*)
----------
	 2

[email protected]>select sql_text,sql_id,version_count,executions from v$sqlarea where sql_text like ‘select count(*) from t1 %‘;

SQL_TEXT									 SQL_ID 				 VERSION_COUNT EXECUTIONS
-------------------------------------------------------------------------------- --------------------------------------- ------------- ----------
select count(*) from t1 where object_id between 999 and 1000			 5gu397922cuqd					     1		1
select count(*) from t1 where object_id between 999 and 60000			 b8xxw70vja3tn					     1		1
select count(*) from t1 where object_id between :x and :y			 9dhu3xk2zu531					     1		1

从上述查询结果可以看到,Oracle在第一次执行上述等价SQL时也是用的硬解析

从执行计划看,此时是对索引IDX_T1走的索引范围扫描,而且Oracle评估出来执行这个索引范围扫描所返回结果集的Cardinality的值为3。并注意到“Peeked Binds”部分的内容为“1 - :X (NUMBER): 999 2 - :Y (NUMBER): 1000”,这说明Oracle在硬解析上述SQL的过程中确实使用了绑定变量窥探,且做“窥探”这个动作时看到的绑定变量x和y的具体输入值分别为999和1000。

现在保持x不变,将y修改为60000:

[email protected]>exec :y := 60000;

PL/SQL procedure successfully completed.

[email protected]>select count(*) from t1 where object_id between :x and :y;

  COUNT(*)
----------
     58180

从上述查询结果可以看出上述SQL对应的VERSION_COUNT的值为1,列EXECUTIONS的值为2,这说明Oracle在第二次执行该SQL时用的是软解析。

从执行计划上可以看出,此时SQL的执行计划依然走的是对索引IDX_T1走的索引范围扫描,并且“Peeked Binds”部分的内容依然为“1 - :X (NUMBER): 999 2 - :Y (NUMBER): 1000”。

之前在不使用绑定变量时,我们已经知道Oracle在执行“between 999 and 60000”条件时走的是索引快速全扫描。但第二次执行使用绑定变量等价改写的SQL时,即使绑定变量x和y的具体的输入值是999和60000,但Oracle这里依然沿用该SQL之前硬解析时(对应绑定量x和y的具体的输入值是999和1000)所产生的解析树和执行计划,而不再重复执行“窥探”的动作。

如果想让上述等价SQL再次走索引快速全扫描,只需要让Oracle再次执行SQL时使用硬解析就行。因为一旦使用硬解析,Oracle就会再执行一次“窥探”的动作。让Oracle再次执行目标SQL时使用硬解析的方法有很多,其中很常见的一种方法是对目标SQL中所涉及的表执行DDL操作。因为一旦对某个表执行了DDL操作,库缓存 中所有在SQL文本中包含了这个表的Shared Cursor都会被Oracle标记为失效(invalid),这意味着这些Shared Cursor中存储的解析树和执行计划将不再能被重用,所以当Oracle再次执行与这个表相关的SQL时就会使用硬解析。这里选择对表添加注释(COMMENT),它也是DDL操作。

对表T1执行COMMENT语句并执行等价SQL

[email protected]>comment on table t1 is ‘Test table for Bind Peeking‘;

Comment created.

[email protected]>select count(*) from t1 where object_id between :x and :y;

  COUNT(*)
----------
     58180

[email protected]>select sql_text,sql_id,version_count,executions from v$sqlarea where sql_text like ‘select count(*) from t1 %‘;

SQL_TEXT									 SQL_ID 				 VERSION_COUNT EXECUTIONS
-------------------------------------------------------------------------------- --------------------------------------- ------------- ----------
select count(*) from t1 where object_id between 999 and 1000			 5gu397922cuqd					     1		1
select count(*) from t1 where object_id between 999 and 60000			 b8xxw70vja3tn					     1		1
select count(*) from t1 where object_id between :x and :y			 9dhu3xk2zu531					     1		1

从上面的查询结果可以看到等价SQL对应的列VERSION_COUNT的值为1,列EXECUTIONS的值由之前的2变为了现在的1,说明Oracle在第三次执行该SQL时用的是硬解析(EXECUTIONS的值为1,是因为Oracle在这里重新生成了一对Parent Cursor和Child Cursor,原先EXECUTIONS的值为2所对应的Shared Cursor已经被Oracle标记为invalid,相当于被废弃了)。

从执行计划可以看出,现在执行计划走的是索引快速全扫描,而Oracle评估出来执行这个索引快速全扫描所返回结果集的Cardinality的值为57646。并且“Peeked Binds”部分的内容依然为“1 - :X (NUMBER): 999 2 - :Y (NUMBER): 60000”。说明Oracle在执行上述SQL的过程中确实又一次使用了绑定变量窥探,且做“窥探”这个动作时看到的绑定变量x和y的具体输入值分别为999和60000。

现在把隐含参数_OPTIM_PEEK_USER_BINDS的值设为FALSE以关闭绑定变量窥探:

[email protected]>alter session set "_optim_peek_user_binds"=false;

Session altered.

然后保持x的值不变,将y值修改为1000

[email protected]>select count(*) from t1 where object_id between :x and :y;

  COUNT(*)
----------
	 2

[email protected]>select sql_text,sql_id,version_count,executions from v$sqlarea where sql_text like ‘select count(*) from t1 %‘;

SQL_TEXT									 SQL_ID 				 VERSION_COUNT EXECUTIONS
-------------------------------------------------------------------------------- --------------------------------------- ------------- ----------
select count(*) from t1 where object_id between 999 and 1000			 5gu397922cuqd					     1		1
select count(*) from t1 where object_id between 999 and 60000			 b8xxw70vja3tn					     1		1
select count(*) from t1 where object_id between :x and :y			 9dhu3xk2zu531					     2		2

从上面的查询结果可以看到等价SQL对应的列VERSION_COUNT和列EXECUTIONS的值均由1变为了现在的2,说明Oracle在第四次执行该SQL时使用硬解析。VERSION_COUNT的值为2,意味着该SQL所在的Parent Cursor下挂了两个Child Cursor。从如下查询结果可以看出该SQL确实有两个Child Cursor:

[email protected]>select plan_hash_value,child_number from v$sql where sql_id=‘9dhu3xk2zu531‘;

PLAN_HASH_VALUE CHILD_NUMBER
--------------- ------------
     1410530761 	   0
     2351893609 	   1

显然,我们把绑定变量窥探关闭后再次执行SQL时所对应的解析权和执行计划应该存储在CHILD_NUMBER为1的Child Cursor中。查看执行计划

从执行计划可以看出Oracle此时的执行计划已经从之前的索引快速全扫描变为 现在的索引范围扫描。而且Oracle评估出来执行这个索引范围扫描所返回结果集的Cardinality的值为180。注意Outline Data部分有“OPT_PARAM(‘_optim_peek_user_binds‘ ‘false‘)”,而且执行计划中没有“Peeking Binds”部分内容,说明此时Oracle已经禁用了绑定变量窥探。

前面已经介绍了使用DDL操作可以让Oracle再次执行SQL时使用硬解析,但这种方法的弊端在于其影响范围还是太广,因为一旦对某个表执行了DDL操作,再次执行与这个表相关的所有SQL时就会全部使用硬解析。这是很不好的,特别是对于OLTP类型的应用系统而言,因为这可能会导致短时间内的硬解析数量剧增,进而影响系统的性能。

下面再来介绍一种就去让Oracle再次执行目标SQL时使用硬解析,但其影响范围公限于目标SQL所对应的Shared Cursor,也就是说它可以做到让Oracle在执行目标SQL时使用硬解析,在执行其他所有SQL时都和原来一样保持不变。

这种方法就是使用DBMS_SHARED_POOL.PURGE。它是从Oracle 10.2.0.4开始引入的一种方法,它可以用来删除指定的缓存在库缓存中的Shared Cursor。DBMS_SHARED_POOL.PURGE可以让Oracle在执行目标SQL时使用硬解析的原理是显而易见的——如果某个SQL对应的Shared Cursor被删除了,Oracle再次执行该SQL时自然就会使用硬解析了。

查看目标SQL对应的ADDRESS和HASH_VALUE值:

[email protected]>select sql_text,sql_id,version_count,executions,address,hash_value from v$sqlarea where sql_text like ‘select count(*) from t1 %‘;

SQL_TEXT									 SQL_ID 				 VERSION_COUNT EXECUTIONS ADDRESS	   HASH_VALUE
-------------------------------------------------------------------------------- --------------------------------------- ------------- ---------- ---------------- ----------
select count(*) from t1 where object_id between 999 and 1000			 5gu397922cuqd					     1		1 00000000B4D1B130 1143368397
select count(*) from t1 where object_id between 999 and 60000			 b8xxw70vja3tn					     1		1 00000000B4D1AA90  924127028
select count(*) from t1 where object_id between :x and :y			 9dhu3xk2zu531					     2		2 00000000B4CC4840 2247955553

使用dbms_shared_pool.purge删除目标SQL的Shared Cursor:

[email protected]>exec sys.dbms_shared_pool.purge(‘00000000B4CC4840,2247955553‘,‘c‘);

PL/SQL procedure successfully completed.

[email protected]>select sql_text,sql_id,version_count,executions,address,hash_value from v$sqlarea where sql_text like ‘select count(*) from t1 %‘;

SQL_TEXT									 SQL_ID 				 VERSION_COUNT EXECUTIONS ADDRESS	   HASH_VALUE
-------------------------------------------------------------------------------- --------------------------------------- ------------- ---------- ---------------- ----------
select count(*) from t1 where object_id between 999 and 1000			 5gu397922cuqd					     1		1 00000000B4D1B130 1143368397
select count(*) from t1 where object_id between 999 and 60000			 b8xxw70vja3tn					     1		1 00000000B4D1AA90  924127028

从上述查询结果可以看出,dbms_shared_pool.purge确实已经删除了目标sql对应的Shared Cursor。

需要注意的是,如果在10.2.0.4中使用dbms_shared_pool.purge,则在使用之前必须特工设置event 5614566(alter session set events ‘5614566 trace name context forever‘),否则dbms_shared_pool.purge将不起作用,这个限制在10.2.0.4以上的版本中已经不存在了。

现在保持x值不变,将y修改为60000,并执行目标SQL:

[email protected]>exec :y := 60000;

PL/SQL procedure successfully completed.

[email protected]>select count(*) from t1 where object_id between :x and :y;

  COUNT(*)
----------
     58180
     
[email protected]>select sql_text,sql_id,version_count,executions from v$sqlarea where sql_text like ‘select count(*) from t1 %‘;

SQL_TEXT									 SQL_ID 				 VERSION_COUNT EXECUTIONS
-------------------------------------------------------------------------------- --------------------------------------- ------------- ----------
select count(*) from t1 where object_id between 999 and 1000			 5gu397922cuqd					     1		1
select count(*) from t1 where object_id between 999 and 60000			 b8xxw70vja3tn					     1		1
select count(*) from t1 where object_id between :x and :y			 9dhu3xk2zu531					     2		1

[email protected]>select plan_hash_value,child_number from v$sql where sql_id=‘9dhu3xk2zu531‘;

PLAN_HASH_VALUE CHILD_NUMBER
--------------- ------------
     2351893609 	   0

从上面的查询结果可以看到该SQL对应的VERSION_COUNT的值为2,EXECUTIONS的值为1。EXECUTIONS的值为1说明Oracle在执行些SQL时确实是在用硬解析,但VERSION_COUNT的值应该为1才对,从查询中也看到目标SQL的Parent Cursor下确实只挂了一个Child Cursor,所以VERSION_COUNT的值应该是1而不是2(也许是Oracle的BUG,不再深究)。

从执行计划中可以看出,Oracle此时仍然选择索引范围扫描,而且Oracle评估出来执行这个索引范围扫描返回的结果集的Cardinality的值依然是180。

这意味着当我们把绑定变量窥探关闭后,无论对绑定变量x和y传入的值是多少,都不会影响Oracle对于目标SQL执行计划的选择。这也契合了之前提到的:如果不使用绑定变量窥探,则对那些可选择率可能会随着具体输入值的变化的谓词条件而言,Oracle会使用默认的可选择率(例如5%)。

那180是如何计算出来的呢?

对于上述SQL其where条件的Selectivity和Cardinality的计算公式如下所示:

Cardinality = NUM_ROWS * Selectivity

Selectivity = 0.05*0.05

上述公式适用于禁用了绑定变量窥探且where条件为“目标列between x and y”的Selectivity和Cardinality的计算

NUM_ROWS表示目标列所在列的记录数

where条件为“目标列between x and y”,相当于“目标列>=x and 目标列<=y”,对于“目标列>=x and 目标列<=y”而言,Oracle均会使用5%的可选择率,所以可选择率就是0.05*0.05。

代入公式进行计算,计算结果为180。

[email protected]>select table_name,num_rows from dba_tables where owner=user and table_name=‘T1‘;

TABLE_NAME										     NUM_ROWS
------------------------------------------------------------------------------------------ ----------
T1												72005

[email protected]>select round(72005*0.05*0.05) from dual;

ROUND(72005*0.05*0.05)
----------------------
		   180

参考:《基于Oracle的SQL优化》

时间: 2024-11-16 17:35:51

Oracle绑定变量窥探的相关文章

查看真实的执行计划 绑定变量对执行计划的影响--“绑定变量窥探”

--##################################################### --####     AWR执行计划                             #### --##################################################### SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_AWR('8qfs8857jc8fw',NULL,NULL,'ADVANCED')); SEL

Oracle绑定变量

select * from table where id = ? 类似于上面这样的sql,如果不用绑定变量,每次执行时Oracle会认为是不同的sql,会在每次执行时生成一遍执行计划,而执行计划的生成是非常耗CPU,试想一下,如果1000个并发都在执行这条语句,等于同时在生成1000个执行计划. 如果使用了绑定变量,那么即使id的值在变化,Oracle也认为是同一个语句,只在第一次生成一遍执行计划,保存到共享池中.后面的999次执行都不需要再生成执行计划,直接用就可以. CPU性能是一方面,另一

Oracle 数据库的绑定变量特性及应用

Oracle 数据库的绑定变量特性及应用[-----]转载自https://www.cnblogs.com/rootq/(原地址) 关键词: 绑定变量(binding variable),共享池(shared buffer pool), SGA(system global area); 在开发一个数据库系统前,有谁对Oracle 系统了解很多,尤其是它的特性,好象很少吧;对初学者来讲,这更是不可能的事情;仅仅简单掌握了SQL的写法,就开始了数据库的开发,其结果只能是开发一个没有效率,也没有可扩展

通过重新生成执行计划解决绑定变量执行计划偏差导致SQL执行时间过长

基本要素(时间.用户.问题) 用户11g环境下有段SQL语句在程序中执行效率非常差,但是在plsql中执行却很快,通过查看执行计划,发现使用了不同的索引导致,程序中执行的如下: PLSQL中执行的效果如下: 可以看到差别,使用门诊费用记录_IX_登记时间索引是在plsql中的执行计划,使用门诊费用记录_UQ_NO的是程序中的执行计划,两者SQL是完全相同的,唯一却别就是前者使用了绑定变量,后者是直接带参数值执行. 问题分析 问题很明显,由于绑定变量生成的执行计划与实际有偏差,11g本来有个绑定变

分页查询中绑定变量

在分页查询时,oracle绑定变量的使用优化 var a numbervar b numbervar c numberexec :a:=0exec :b:=10exec :c:=0set serveroutput off;alter session set statistics_level=all;SELECT * FROM (SELECT T1_.*, rownum ROWNUM_ FROM ( SELECT O.* FROM yyf.testa O WHERE 1=1 AND O.CREAT

Oracle之绑定变量 2

绑定变量(bind variable)我们经常在写plsql中用到,那我们承接上一节的来看看在plsql中静态sql和动态sql在绑定变量的区别 declare     v_value_a varchar2(100);     v_value_b varchar2(100);     v_name  varchar2(50); begin     v_value_a := 'SMITH';     select e.ename/*+test*/into v_name from scott.emp

oracle中关于替代变量,accpt,绑定变量,字符变量

此文档介绍两个事情,一个是替代变量,另一个就是了解一下硬解析和软解析对于变量来说declare定义的好还是variable定义的好 在oracle 中,对于一个提交的sql语句,存在两种可选的解析过程, 一种叫做硬解析,一种叫做软解析.一个硬解析需要经解析,制定执行路径,优化访问计划等许多的步骤.硬解释不仅仅耗费大量的cpu,更重要的是会占据重要的们闩(latch)资源,严重的影响系统的规模的扩大(即限制了系统的并发行),而且引起的问题不能通过增加内存条和cpu的数量来解决.之所以这样是因为门闩

Oracle 10g中一个关于绑定变量和非绑定变量的测试对比

首先创建测试表并记录解析统计数据: 进行循环插入数据,以下代码并未使用绑定变量: 发现在增加了11个硬解析. 查询v$sqlarea视图,可以找到这些不能共享的SQL,注意每条SQL都只执行了一次,这些SQL不仅解析要消耗密集的SQL资源,也要占用共享内存存储这些不同的SQL代码: 重建测试表,进行第二次测试: 这一次使用绑定变量,同样10次数据插入: 现在看一下SQL解析的统计数据库,硬解析由原来的145增加到147. 对于该SQL,共享池中只存在一份,解析一次,执行10次,这就是绑定变量的优

Oracle 学习之性能优化(三)绑定变量

根据Oracle 学习之性能优化(二)游标中的描述,我们知道如下两条语句是不共享的. select * from emp where empno=7698; select * from emp where empno=7566; 这样就造成每次执行用户的查询都要进行硬解析,但是我们知道,其他这两个语句的执行计划应该是相同.那么有什么方法能避免不必要的硬解析吗?这里我们提供2种方法. 一.绑定变量 SQL> variable empno number; SQL> exec :empno := 7