TOM大叔的调优书中说过一句话,具体英文就忘了,大概意思就是:如果有人让我写本怎样让ORACLE性能最慢的书的话我会将取消绑定变量(bind variable)做为书的第一章和最后一章(他的意思是他很有幽默~~!),可见绑定变量的重要性。
绑定变量大多用在OLTP(online transaction process)中,在OLAP(online analizy process)中就没必要用BIND VARIABLE了。
要了解绑定变量的作用首先得了解下SHARED_POOL library cache中的SQL解析。
SQL 解析分为硬解析(hard parse)跟软解析(soft parse)。所谓硬解析就是真正的将产生的SQL用自己内部的算法完全解析一遍并将解析后的结果存入库缓存(library cache),而软解析就是发现库缓存中已经存在解析过的该SQL语句,从而直接利用,跳过重新解析的步骤。
所以SQL解析的步骤就是:先判断库缓存中是否存在该sql的解析,存在就不用再解析(软解析),没有就解析一遍(硬解析),并存入库缓存。
下面做个实验:
SQL> select * from v$sysstat where statistic#=331;
STATISTIC# NAME CLASS VALUE STAT_ID
---------- -------------------- ---------- ---------- ----------
331 parse count (hard) 64 276 143509059
SQL> select * from v$sysstat where statistic#=331;
STATISTIC# NAME CLASS VALUE STAT_ID
---------- -------------------- ---------- ---------- ----------
331 parse count (hard) 64 276 143509059
--这里value记录的就是硬解析次数,上面这个select语句同样第一次会被解析,之后就软解析了。
SQL> select * from v$sysstat where statistic#=331;
STATISTIC# NAME CLASS VALUE STAT_ID
---------- -------------------- ---------- ---------- ----------
331 parse count (hard) 64 281 143509059
-- 过会再执行此句,可以看到这里VALUE变成281了,有人可能会有疑问,不是276的吗,怎么变了?原因是库内部平时也在做各种操作,比如状态收集等, 可能会产生硬解析,而且执行一段时间之后,ORACLE会根据最近最少使用原则将此SQL解析踢出共享池,再执行就要重新解析一遍了。
--为了尽量不影响我们的分析,将库以只读启动:
SQL> shutdown immediate
数据库已经关闭。
已经卸载数据库。
ORACLE 例程已经关闭。
SQL> startup mount
ORACLE 例程已经启动。
Total System Global Area 167772160 bytes
Fixed Size 1247900 bytes
Variable Size 62915940 bytes
Database Buffers 100663296 bytes
Redo Buffers 2945024 bytes
数据库装载完毕。
SQL> alter database open read only
2 ;
数据库已更改。
SQL> col name for a20
SQL> select * from v$sysstat where statistic#=331;
STATISTIC# NAME CLASS VALUE STAT_ID
---------- -------------------- ---------- ---------- ----------
331 parse count (hard) 64 188 143509059
SQL> select * from v$sysstat where statistic#=331;
STATISTIC# NAME CLASS VALUE STAT_ID
---------- -------------------- ---------- ---------- ----------
331 parse count (hard) 64 188 143509059
SQL> select * from t where id=2009;
ID
----------
2009
SQL> select * from v$sysstat where statistic#=331;
STATISTIC# NAME CLASS VALUE STAT_ID
---------- -------------------- ---------- ---------- ----------
331 parse count (hard) 64 190 143509059
--解析id=2009的查询
SQL> select * from t where id=2009;
ID
----------
2009
SQL> select * from v$sysstat where statistic#=331;
STATISTIC# NAME CLASS VALUE STAT_ID
---------- -------------------- ---------- ---------- ----------
331 parse count (hard) 64 190 143509059
--此前解析过,所以再此查询2009时VALUE不变,软解析。
SQL> select * from t where id= 2009;
ID
----------
2009
SQL> select * from v$sysstat where statistic#=331;
STATISTIC# NAME CLASS VALUE STAT_ID
---------- -------------------- ---------- ---------- ----------
331 parse count (hard) 64 191 143509059
--此处注意,2009前面有个空格,也会被判断为跟原来的SQL不匹配。
SQL> select * from t where Id=2009;
ID
----------
2009
SQL> select * from v$sysstat where statistic#=331;
STATISTIC# NAME CLASS VALUE STAT_ID
---------- -------------------- ---------- ---------- ----------
331 parse count (hard) 64 192 143509059
--此处的Id的I是大写,也会跟原来的查询不匹配,所以硬解析。
SQL> select * from t where id=2010;
ID
----------
2010
SQL> select * from v$sysstat where statistic#=331;
STATISTIC# NAME CLASS VALUE STAT_ID
---------- -------------------- ---------- ---------- ----------
331 parse count (hard) 64 194 143509059
--2010跟2009的查询SQL解析不共享,所以也要硬解析。
SQL> show parameter cursor_sharing
NAME TYPE VALUE
------------------------------------ ---------------------- ---------
cursor_sharing string EXACT
此外,cursor_sharing参数也会影响ORACLE判断SQL是否共享,此处用的是EXACT,也是10G默认的,11G中已不是。以后我会单独讨论下这个参数。
可以想象,如果对10W个不同的ID每个都查询一次,硬解析的次数是很大的,会导致性能的下降。而用绑定变量来代替ID的值,10W条数据的查询会被共享为一条SQL,只硬解析一次,带来的性能提升也是很明显的。
下面也对绑定变量做个实验,向T表插入10W条数据。可以看出用绑定变量跟不用的巨大差异。
SQL> set timing on
SQL> truncate table t;
Table truncated.
Elapsed: 00:00:00.04
--p1不用绑定变量
SQL> create or replace procedure p1
2 as
3 begin
4 for i in 1..100000 loop
5 execute immediate 'insert into t values('||i||')';
6 end loop;
7 end;
8 /
Procedure created.
Elapsed: 00:00:00.06
--p2用绑定变量
SQL> create or replace procedure p2
2 as
3 begin
4 for i in 1..100000 loop
5 execute immediate 'insert into t values(:a)' using i;
6 end loop;
7 end;
8 /
Procedure created.
Elapsed: 00:00:00.04
SQL> select * from v$sysstat where statistic#=331;
STATISTIC# NAME CLASS VALUE STAT_ID
---------- -------------------- ---------- ---------- ----------
331 parse count (hard) 64 143800 143509059
1 row selected.
Elapsed: 00:00:00.01
SQL> exec p1;
PL/SQL procedure successfully completed.
Elapsed: 00:01:10.01
SQL> select * from v$sysstat where statistic#=331;
STATISTIC# NAME CLASS VALUE STAT_ID
---------- -------------------- ---------- ---------- ----------
331 parse count (hard) 64 243800 143509059
1 row selected.
Elapsed: 00:00:00.01
SQL> truncate table t;
Table truncated.
Elapsed: 00:00:00.12
SQL> select * from v$sysstat where statistic#=331;
STATISTIC# NAME CLASS VALUE STAT_ID
---------- -------------------- ---------- ---------- ----------
331 parse count (hard) 64 243801 143509059
1 row selected.
Elapsed: 00:00:00.03
SQL> exec p2;
PL/SQL procedure successfully completed.
Elapsed: 00:00:14.67
SQL> select * from v$sysstat where statistic#=331;
STATISTIC# NAME CLASS VALUE STAT_ID
---------- -------------------- ---------- ---------- ----------
331 parse count (hard) 64 243803 143509059
1 row selected.
Elapsed: 00:00:00.00
可见差异之大。。
做 实验的时候发现过程p2中用insert into t values(i)代替execute immediate 'insert into t values(:a)' using i的话,消耗的时间是相近的,也就是说前者也会被替换成使用绑定变量的方式。感兴趣的朋友可以试一下。不足的地方欢迎指正~
转自:http://hi.baidu.com/kywinder/item/8dc7972656a6019db7326341