oracle绑定变量学习

绑定变量(binding variable)

一、为什么要绑定变量

    在oracle 中,对于一个提交的sql语句,存在两种可选的解析过程, 一种叫做硬解析,一种叫做软解析.
一个硬解析需要经解析,制定执行路径,优化访问计划等许多的步骤.硬解释不仅仅耗费大量的cpu,更重要的是会占据重要的门闩(latch)资源,
严重的影响系统的规模的扩大(即限制了系统的并发行), 而且引起的问题不能通过增加内存条和cpu的数量来解决。

   当一个sql语句提交后,oracle会首先检查一下共享缓冲池(shared pool)里有没有与之完全相同的语句,
如果有的话只须执行软分析即可,否则就得进行硬分析。而唯一使得oracle 能够重复利用执行计划的方法就是采用绑定变量。
绑定变量能够使得每次提交的sql语句都完全一样。

   在Oracle中存在两种类型的SQL语句,一类为DDL语句,他们是从来不会共享使用的,也就是每次执行都需要进行硬解析。
还有一类就是DML语句,他们会根据情况选择要么进行硬解析,要么进行软解析。


二、在Oracle中SQL语句的解析步骤如下:
1、 语法检测。判断一条SQL语句的语法是否符合SQL的规范,比如执行:SQL> selet * from emp; 我们就可以看出由于Select关键字少了一个“c”,这条语句就无法通过语法检验的步骤了。
2、 语义检查。语法正确的SQL语句在解析的第二个步骤就是判断该SQL语句所访问的表及列是否准确,用户是否有权限访问或更改相应的表或列。
3、 检查共享池中是否有相同的语句存在。假如执行的SQL语句已经在共享池中存在同样的副本,那么该SQL语句将会被软解析,也就是可以重用已解析过的语句的 执行计划和优化方案,可以忽略语句解析过程中最耗费资源的步骤。这个步骤又可以分为两个步骤:
(1)验证SQL语句是否完全一致。
     Oracle将会对传递进来的SQL语句使用HASH函数运算得出HASH值,再与共享池中现有语句的 HASH值进行比较看是否一一对应。
     现有数据库中SQL语句的HASH值我们可以通过访问v$sql、v$sqlarea、v$sqltext等数据字典中的HASH_VALUE列查询得出。

(2)验证SQL语句执行环境是否相同。比如同样一条SQL语句,一个查询会话加了/*+ first_rows */的HINT,
另外一个用户加/*+ all_rows */的HINT,他们就会产生不同的执行计划,尽管他们是查询同样的数据。

4、 Oracle根据提交的SQL语句再查询相应的数据对象是否有统计信息。
    如果有统计信息的话,那么CBO将会使用这些统计信息产生所有可能的执行计划(可能多达成千上万个)和相应的Cost,
最终选择Cost最低的那个执行计划。如果查询的数据对象无统计信息,则按RBO的默认规则选择相应的执行计划。
这个步骤也是解析中最耗费资源的,因此我们应该极力避免硬解析的产生。至此,解析的步骤已经全部完成,
Oracle将会根据解析产生的执行计划执行SQL语句和提取相应的数据。


三、绑定变量示例
普通sql语句:
SQL> select * from mytest where random_id=30;
SQL> select * from mytest where random_id=31;
SQL> select * from mytest where random_id=32;

含绑定变量的sql 语句:
SQL> select * from mytest where random_id=:x;
SQL> select * from mytest where random_id='&random_id';

Sql*plus 中使用绑定变量:
SQL> set timing on
SQL> variable y number;
SQL> exec :y :=39;
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.01
SQL> select * from mytest where random_id=:y;
SQL> exec :y :=21;
PL/SQL procedure successfully completed.
SQL> select * from mytest where random_id=:y;   ---改变y的值,重复执行此sql

pl/sql中使用绑定变量:
pl/sql很多时候都会自动绑定变量,如下例:
SQL> create or replace procedure p_mytest(p_no in number)
as
  begin
    update mytest set random_id=random_id+2 where random_id = p_no;
    commit;
  end;
/
Procedure created.
Elapsed: 00:00:00.17

SQL> exec p_mytest(20);   ---执行该存储过程
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.02

在pl/sql中,引用变量即是引用绑定变量.
但在pl/sql中动态sql并不是这样,在vb,java以及其他应用程序中都得显式地利用绑定变量。

SQL> select sql_id,hash_value,plan_hash_value,executions,sql_text from v$sql where upper(sql_text) like 'SELECT * FROM MYTEST%';

SQL_ID        HASH_VALUE PLAN_HASH_VALUE      EXECUTIONS SQL_TEXT
------------- ---------- --------------- ---------- --------------------------------------------------------------------------------
fvbfv6r7xhrx0 3487063968      1137542690          5 select * from mytest where ROWNUM <10
cw7d99wy2d7sa 1009164042      1137542690          3 select * from mytest where ROWNUM <5
6dwn6j9cw1dp2 1505801890      1692938441          2 select * from mytest where random_id=:x    ---包含一次软解析
g7mfz99r6dfpw 1852226236      1692938441          1 select * from mytest where random_id='39'
8v2bw6pq8tx7m 1821177075      1692938441          0 select * from mytest where random_id=30
0zyay1mv7kvys 4135153624      1137542690          2 select * from mytest where rownum<5
bdbtr8cb7fzka  376929866      1692938441          1 select * from mytest where random_id=20

说明:每条语句都有一个唯一的HASH_VALUE,相当于你语句的一个ID,
v$sql和v$sql_plan的hash_value,如果两条相同的话ORACLE会认为这两条语句是同一条语句,不会再次做硬解析,plan_hash_value是执行计划的hash值,数值的大小没啥意义。

查看某个sql具体的执行计划:
SQL> select plan_table_output from table(dbms_xplan.display_awr('g7mfz99r6dfpw'));
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
SQL_ID g7mfz99r6dfpw
--------------------
select * from mytest where random_id='39'
Plan hash value: 1692938441
----------------------------------------------------------------------------
| Id  | Operation         | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |        |       |       |    24 (100)|          |
|   1 |  TABLE ACCESS FULL| MYTEST |   107 |   213K|    24   (0)| 00:00:01 |
----------------------------------------------------------------------------
Note
-----
   - dynamic sampling used for this statement (level=2)
17 rows selected


1.对比区别
1.1 未绑定变量
set echo on;
set serveroutput on; (把执行结果显示出来)
set timing on(打开记时器.)

SQL> declare
  type rc is ref cursor;
  l_rc rc;
  l_du all_objects.OBJECT_NAME%type;
  l_ti number default dbms_utility.get_time;   --dbms_utility.get_time本身是一个时间的数值
begin
  for i in 1 .. 1000 loop
    open l_rc for 'select object_name from all_objects where object_id=' || i;
    fetch l_rc into l_du;
  close l_rc;
  end loop;
  dbms_output.put_line(round((dbms_utility.get_time-l_ti)/100,2) ||'seconds');  --通过差集/100 可以大概预估整个过程执行时间
end;
/
64.2seconds
PL/SQL procedure successfully completed.
Elapsed: 00:01:04.31                      ---用时64s

1.2 绑定变量
SQL> declare
  type rc is ref cursor;
  l_rc rc;
  l_du all_objects.OBJECT_NAME%type;
  l_ti number default dbms_utility.get_time;
begin
  for i in 1 .. 1000 loop
    open l_rc for 'select object_name from all_objects where object_id=:x' using i;
    fetch l_rc  into l_du;
  close l_rc;
  end loop;
  dbms_output.put_line(round((dbms_utility.get_time-l_ti)/100,2) ||'seconds');
end;
/
.52seconds

PL/SQL procedure successfully completed.
Elapsed: 00:00:00.55                    ---用时不超过1s


2)绑定方法
2.1 静态绑定变量
set serveroutput on;
set timing on;
declare
  l_num number;
  l_p1  int;
begin
  l_p1:=5;
  select count(*) into l_num from mytest where random_id = l_p1;
  dbms_output.put_line(l_num);
end;
/
94
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.01
在上面的情况,Oracle会自己绑定变量,select语句只会编译一次。

2.2 动态绑定变量
declare
  l_sql varchar2(2000);
  l_num number;
  l_p1  int;
begin
  l_p1:=5;
  l_sql:='select count(*) into :z from mytest where random_id =:y';
  execute immediate l_sql into l_num using l_p1;
  dbms_output.put_line(l_num);
end;
/
94
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.00

2.3 dbms_output的绑定变量使用
declare
  l_id  integer;
  l_ti  number default dbms_utility.get_time;
  l_num integer;
begin
  l_id := dbms_sql.open_cursor;
  for i in 1 .. 1000 loop
    dbms_sql.parse(l_id,'insert into t3 values(:username,:user_id,sysdate)',dbms_sql.v7);
    dbms_sql.bind_variable(l_id, 'username', 'test' || to_char(i));
    dbms_sql.bind_variable(l_id, 'user_id', i);
    l_num := dbms_sql.execute(l_id);
  end loop;
  dbms_sql.close_cursor(l_id);
  dbms_output.put_line(round((dbms_utility.get_time - l_ti) / 100, 2) ||'seconds');
end;
/
.4seconds
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.41                        ---用时0.4s

查看数据:
SQL> select count(*) from t3;
  COUNT(*)
----------
      1000

SQL> select * from t3 where rownum <5;
USERNAME   USER_ID RECORD_TIME
------------------------------ ---------- ------------------------------
test879       879 25-DEC-15 03.46.31.000000 PM
test880       880 25-DEC-15 03.46.31.000000 PM
test881       881 25-DEC-15 03.46.31.000000 PM
test882       882 25-DEC-15 03.46.31.000000 PM

说明:
     dbms_sql.parse(v_cursor, v_sql, dbms_sql.native); --解析动态SQL语句;
     dbms_sql.bind_variable(v_cursor, ':sid', s_id);   --绑定输入参数;

四、查询占用资源较多的sql
select sql_text ,sharable_mem from v$sql where sharable_mem > '100000' order by sharable_mem ;   --占用内存超过100k的sql内容

五. 绑定变量使用限制条件是什么?
    为了不重复解析相同的SQL语句,在第一次解析之后, ORACLE将SQL语句存放在内存中.这块位于系统全局区域SGA的共享池(shared buffer pool)中
的内存可以被所有的数据库用户共享. 因此,当你执行一个SQL语句(有时被称为一个游标)时,如果它和之前的执行过的语句完全相同,
ORACLE就能很快获得已经被解析的语句以及最好的执行路径. ORACLE的这个功能大大地提高了SQL的执行性能并节省了内存的使用.

   数据库管理员必须在init.ora中为这个区域设置合适的参数,当这个内存区域越大,就可以保留更多的语句,当然被共享的可能性也就越大了.
当你向ORACLE 提交一个SQL语句,ORACLE会首先在这块内存中查找相同的语句.这里需要注明的是,ORACLE对两者采取的是一种严格匹配,
要达成共享,SQL语句必须完全相同(包括空格,换行等).
共享的语句必须满足三个条件:

A. 字符级的比较:
当前被执行的语句和共享池中的语句必须完全相同.
例如:
SELECT * FROM EMP;
和下列每一个都不同
SELECT * from EMP;
Select * From Emp;
SELECT * FROM EMP;

B. 两个语句所指的对象必须完全相同:
例如:tina.mytest

C. 两个SQL语句中必须使用相同的名字的绑定变量(bind variables)
下面两个语句是不同的(即使在运行时,赋于不同的绑定变量相同的值)
select * from mytest where random_id=:x;
select * from mytest where random_id=:y;


补充:在Java中的使用绑定变量
Java代码
PreparedStatement pstmt = conn.prepareStatement("select * from table_name where id = ?" );;  
pstmt.setInt(1 ,1 );;  
rset = pstmt.executeQuery();;  
...  
pstmt.setInt(1 ,2 );;  
rset = pstmt.executeQuery();;  

这样Oracle数据库就知道你实际上用的都是同一条sql语句,会以这样的形式:
select * from table_name where id = :1

解析执行后存放在sql区域里面,当以后再有一样的sql的时候,把参数替换一下,就立刻执行,不需要再解析sql了。
既加快了sql执行速度,也不会占有过多SGA的share pool。

String v_id = 'xxxxx';
String v_sql = 'select name from ta where id = ? '; //嵌入绑定变量
stmt = con.prepareStatement( v_sql );
stmt.setString(1, v_id ); //为绑定变量赋值
stmt.executeQuery();
在Java中,结合使用setXXX 系列方法,可以为不同数据类型的绑定变量进行赋值,从而大大优化了SQL 语句的性能。

猜你喜欢

转载自906179271.iteye.com/blog/2266636
今日推荐