plsql动态绑定


13. 批绑定

13.1 本地批绑定

  问题产生的原因:由于SQL引擎和PLSQL引擎来回切换而造成的上下切换而引发性能开销

批绑定:在SQL语句中为PLSQL变量赋值,而一次性将整个集合绑定,不是通过游标循环的方式,从而减少数据往返的次数。
           用了批绑定之后,在SQL引擎和PLSQL引擎之间的上下文切换次数会减少,提高系统的性能

案例:

declare
  type region_rec is record(region_id number(4),region_name varchar2(10));       --定义一个记录类型,region
  type region_tb1 is table of region_rec index by binary_integer;        --定义一个index-by表类型
  region_resc region_tb1;            --定义一个index-by变量 region_resc
  ret_code number;                
  ret_errmsg varchar2(200);
  procedure load_regions(region_resc in region_tb1,retcd out number,errmsg out varchar2) is
  begin
    delete from region_tab;
    commit;
    for i in region_resc.first..region_resc.last loop
      insert into region_tab values(region_resc(i).region_id,region_resc(i).region_name);
    end loop;
    commit;
  exception when others then
    retcd:=sqlcode;
    errmsg:=sqlerrm;
  end;
begin
  for i in 1..5 loop
    region_resc(i).region_id:=i;
    region_resc(i).region_name:=‘region‘||i;
  end loop;
  load_regions(region_resc,ret_code,ret_errmsg);
exception when others then
  dbms_output.put_line(ret_errmsg);
end;

测试:

select * from region_tab;

将上面的程序改写为本地批绑定的方式:

declare
  type region_id_tb1 is table of number index by binary_integer;
  type region_name_tb1 is table of varchar2(10) index by binary_integer;
  region_ids region_id_tb1;
  region_names region_name_tb1;
  ret_code number;
  ret_errmsg varchar2(200);
  procedure load_regions(region_ids in region_id_tb1,region_names in region_name_tb1,retcd out number,errmsg out varchar2) is
  begin
    delete from region_tab;
    commit;
    forall i in region_ids.first..region_ids.last insert into region_tab values(region_ids(i),region_names(i));
    --forall不是循环,因为没有loop,是一种循环算法的替代,也不能将forall作为游标来使用
    commit;
  exception when others then
    retcd:=sqlcode;
    errmsg:=sqlerrm;
  end;
begin
  for i in 1..6 loop
    region_ids(i):=i;
    region_names(i):=‘region‘||i;
    load_regions(region_ids,region_names,ret_code,ret_errmsg);
  end loop;
exception when others then
  dbms_output.put_line(ret_errmsg);
end;

测试forall语句的性能

(1) 将region_tab列的长度改长

SQL> alter table region_tab modify region_id number(8);

Table altered.

SQL> alter table region_tab modify region_name varchar2(100);

Table altered.

(2) 压力测试

declare
  type region_id_tb1 is table of number index by binary_integer;
  type region_name_tb1 is table of varchar2(100) index by binary_integer;
  region_ids region_id_tb1;
  region_names region_name_tb1;
  ret_code number;
  ret_errmsg varchar2(200);
  time1 number;
  time2 number;
  time3 number;
  procedure load_regions_bulk(region_ids in region_id_tb1,region_names in region_name_tb1,retcd out number,errmsg out varchar2) is
  begin
    delete from region_tab;
    commit;
    forall i in 1..1000000 insert into region_tab values(region_ids(i),region_names(i));
    --forall不是循环,因为没有loop,是一种循环算法的替代,也不能将forall作为游标来使用
    commit;
  exception when others then
    retcd:=sqlcode;
    errmsg:=sqlerrm;
  end;
 
  procedure load_regions(region_ids in region_id_tb1,region_names in region_name_tb1,retcd out number,errmsg out varchar2) is
  begin
    delete from region_tab;
    commit;
    for i in 1..1000000 loop
      insert into region_tab values(region_ids(i),region_names(i));
    end loop;
    commit;
  exception when others then
    retcd:=sqlcode;
    errmsg:=sqlerrm;
  end;
begin
  for i in 1..1000000 loop
    region_ids(i):=i;
    region_names(i):=‘region‘||i;
  end loop;
  time1:=dbms_utility.get_time;
  load_regions(region_ids,region_names,ret_code,ret_errmsg);
  time2:=dbms_utility.get_time;
  load_regions_bulk(region_ids,region_names,ret_code,ret_errmsg);
  time3:=dbms_utility.get_time;
  dbms_output.put_line(‘Time without bulk bind ‘||to_char((time2-time1)/100));
  dbms_output.put_line(‘Time with bulk bind ‘||to_char((time3-time2)/100));
exception when others then
  dbms_output.put_line(ret_errmsg);
end;

Time without bulk bind 138.31
Time with bulk bind 54.78

结论:
  用本地批绑定,性能有大幅提升

注意:
 
  A. 传入的参数如果是index-by表,那么index-by表必须是紧密的
  B. 传入的参数必须是index-by,嵌套表,数组等必须是有下标PLSQL变量才可以实现批绑定

##########################################################################################


13.2 批绑定的异常处理

(1) 传统的异常处理的方法

declare

begin
  for i in ... loop
    insert into ....   --执行成功
    insert into ....   --执行失败,跳转到exception处理
    insert into ....   --此句就得不到执行
  end loop;
exception .....
end;

(1) save exceptions 方法捕获到错误的行为而程序不会终止,下面的数据还是可以被插入
   
  save exceptions属性的值保存在一个隐式游标的属性中

  sql%bulk_exceptions中保存错误的行,只有产生错误的这条数据可以被回滚,一旦执行成功,前面提交的数据不受影响,后面的继续执行,依赖上面forall语句继续处理

   error_index给出forall语句失败的索引或者失败的行号 sql%bulk_exceptions(i).error_index
   error_code给出forall语句失败的SQLCODE,引用方式 sqlerrm(-sql%bulk_exceptions(i).error_code);

引入一个错误:ORA-24381

[oracle@oracle254 ~]$ oerr ORA 24381
24381, 00000, "error(s) in array DML"
// *Cause:  One or more rows failed in the DML.
// *Action: Refer to the error stack in the error handle.

把region_tab表的数据清空

SQL> truncate table region_tab;

Table truncated.

SQL> alter table region_tab modify region_id number(4);

Table altered.

SQL> alter table region_tab modify region_name varchar2(10);

Table altered.


declare
  type region_id_tb1 is table of number index by binary_integer;
  type region_name_tb1 is table of varchar2(100) index by binary_integer;
  region_ids region_id_tb1;
  region_names region_name_tb1;
  ret_code number;
  ret_errmsg varchar2(200);
  procedure load_regions_bulk(region_ids in region_id_tb1,region_names in region_name_tb1,retcd out number, errmsg out varchar2) is
    bulk_bind exception;
    pragma exception_init(bulk_bind,-24381);
  begin
    delete from region_tab;
    commit;
    forall i in region_ids.first..region_ids.last save exceptions insert into region_tab values(region_ids(i),region_names(i));
    commit;
    retcd:=0;
    errmsg:=‘successful!‘;
  exception when bulk_bind then
    for i in 1..sql%bulk_exceptions.count loop
      dbms_output.put_line(to_char(sql%bulk_exceptions(i).error_index));
      dbms_output.put_line(sqlerrm(-sql%bulk_exceptions(i).error_code));
    end loop;
    retcd:=sqlcode;
    errmsg:=sqlerrm;
  when others then
    retcd:=sqlcode;
    errmsg:=sqlerrm;
  end;
begin
  for i in 1..10 loop
    region_ids(i):=i;
    region_names(i):=‘region‘||i;
  end loop;
  region_names(3):=‘very very very very very very very very very very very long‘;
  region_names(6):=‘very very very very very very very very very very very very short‘;
  load_regions_bulk(region_ids,region_names,ret_code,ret_errmsg);
  commit;
exception when others then
  dbms_output.put_line(ret_errmsg);
end;

输出:
3
ORA-12899: value too large for column  (actual: , maximum: )
6
ORA-12899: value too large for column  (actual: , maximum: )

SQL> select * from region_tab;

 REGION_ID REGION_NAM
---------- ----------
         1 region1
         2 region2
         4 region4
         5 region5
         7 region7
         8 region8
         9 region9
        10 region10

8 rows selected.

去掉save exceptions,程序发生异常就不会再执行下去

forall语句的属性:
  %found
  %notfound
  %rowcount   --批绑定中,SQL语句全部执行完之后累计处理成功的行数
  %bulk_rowcount  --执行完一次批绑定之后的行数,使用的时候用下标来取值,例如:bulk_rowcount(i)

declare
  type region_id_tb1 is table of number index by binary_integer;
  type region_name_tb1 is table of varchar2(100) index by binary_integer;
  region_ids region_id_tb1;
  region_names region_name_tb1;
  ret_code number;
  ret_errmsg varchar2(200);
  procedure load_regions_bulk(region_ids in region_id_tb1,region_names in region_name_tb1,retcd out number, errmsg out varchar2) is
    bulk_bind exception;
    pragma exception_init(bulk_bind,-24381);
  begin
    delete from region_tab;
    commit;
    forall i in region_ids.first..region_ids.last save exceptions insert into region_tab values(region_ids(i),region_names(i));
    commit;
    retcd:=0;
    errmsg:=‘successful!‘;
  exception when bulk_bind then
    for i in 1..sql%bulk_exceptions.count loop
      dbms_output.put_line(to_char(sql%bulk_exceptions(i).error_index));
      dbms_output.put_line(sqlerrm(-sql%bulk_exceptions(i).error_code));
    end loop;
    for i in 1..region_ids.count loop
      if sql%bulk_rowcount(i)>0 then
        dbms_output.put_line(to_char(sql%bulk_rowcount(i)));
      elsif sql%bulk_rowcount(i)=0 then
        dbms_output.put_line(‘NO ROWS‘);
      end if;
    end loop;
    dbms_output.put_line(to_char(sql%rowcount));
    retcd:=sqlcode;
    errmsg:=sqlerrm;
    dbms_output.put_line(sqlerrm);
  when others then
    retcd:=sqlcode;
    errmsg:=sqlerrm;
    dbms_output.put_line(sqlerrm);
  end;
begin
  for i in 1..10 loop
    region_ids(i):=i;
    region_names(i):=‘region‘||i;
  end loop;
  region_names(3):=‘very very very very very very very very very very very long‘;
  region_names(6):=‘very very very very very very very very very very very very short‘;
  load_regions_bulk(region_ids,region_names,ret_code,ret_errmsg);
  commit;
exception when others then
  dbms_output.put_line(sqlerrm);
end;

3
ORA-12899: value too large for column  (actual: , maximum: )
6
ORA-12899: value too large for column  (actual: , maximum: )
1
1
NO ROWS            --第三行出现错误
1
1
NO ROWS
1
1
1
1

8                    --总共处理成功了多少行

##########################################################################################


13.3 本地批查询

  可以用 bulk collect into collection_name进行批查询

 collection_name可以是index-by,嵌套表,数据

(1) select into

之前写过的程序:
create or replace
procedure update_dyn_global(retcd out number,errmsg out varchar2)
    authid current_user is
    cursor csr_region is select region_name from region_tab;
  begin
    for idx in csr_region loop
      update_dyn_all_table(idx.region_name,retcd,errmsg);
      if retcd<>0 then
        exit;
      end if;
    end loop;
    retcd:=0;
    errmsg:=‘successful!‘;
  exception when others then
    retcd:=sqlcode;
    errmsg:=sqlerrm;
  end;

改写用本地批查询的方式

create or replace
procedure update_dyn_global2(retcd out number,errmsg out varchar2) authid current_user is
    type nametb1 is table of region_tab.region_name%type;
    region_names nametb1;
  begin
    select region_name bulk collect into region_names from region_tab;  --用本地批查询的方式取代游标
    for idx in region_names.first..region_names.last loop
      update_dyn_all_table(region_names(idx),retcd,errmsg);
      if retcd<>0 then
        exit;
      end if;
    end loop;
    retcd:=0;
    errmsg:=‘successful!‘;
  exception when others then
    retcd:=sqlcode;
    errmsg:=sqlerrm;
  end;

测试:

declare
  v_sqlcode number;
  v_sqlerrm varchar2(200);
begin
  update_dyn_global2(v_sqlcode,v_sqlerrm);
  dbms_output.put_line(v_sqlerrm);
end;

(2) fetch语句使用批查询 --在游标中去多行数据的时候

create or replace procedure update_dyn_global3(retcd out number,errmsg out varchar2) authid current_user is
    type nametb1 is table of region_tab.region_name%type;
    region_names nametb1;
    cursor csr_region is select region_name from region_tab;
  begin
    open csr_region;
      fetch csr_region bulk collect into region_names;   --fetch了N行到集合中
      for idx in region_names.first..region_names.last loop
        dbms_output.put_line(region_names(idx));
      end loop;
    close csr_region;
    retcd:=0;
    errmsg:=‘successful!‘;
  exception when others then
    retcd:=sqlcode;
    errmsg:=sqlerrm;
  end;

测试:

declare
  v_sqlcode number;
  v_sqlerrm varchar2(200);
begin
  update_dyn_global3(v_sqlcode,v_sqlerrm);
  dbms_output.put_line(v_sqlerrm);
end;

(3) 使用returning into语句使用批查询  -- update 语句中以批的方式返回新的修改值

declare
  type region_id_tb1 is table of number index by binary_integer;
  type region_name_tb1 is table of varchar2(100) index by binary_integer;
  region_ids region_id_tb1;
  region_names region_name_tb1;
  ret_code number;
  ret_errmsg varchar2(200);
  out_region_names region_name_tb1;
  procedure load_regions_bulk_bind(region_ids in region_id_tb1,region_names in region_name_tb1,retcd out number, errmsg out varchar2) is
    bulk_bind_excep exception;
    pragma exception_init(bulk_bind_excep,-24381);
  begin
    delete from region_tab;
    commit;
    forall i in region_ids.first..region_ids.last save exceptions insert into region_tab values(region_ids(i),region_names(i));
    commit;
    retcd:=0;
    errmsg:=‘successful!‘;
  exception when bulk_bind_excep then
    for i in 1..sql%bulk_exceptions.count loop
      dbms_output.put_line(to_char(sql%bulk_exceptions(i).error_index));
      dbms_output.put_line(sqlerrm(-sql%bulk_exceptions(i).error_code));
    end loop;
    for i in 1..region_ids.count loop
      if sql%bulk_rowcount(i)>0 then
        dbms_output.put_line(to_char(sql%bulk_rowcount(i)));
      elsif sql%bulk_rowcount(i)=0 then
        dbms_output.put_line(‘NO ROWS‘);
      end if;
    end loop;
    dbms_output.put_line(to_char(sql%rowcount));
    retcd:=sqlcode;
    errmsg:=sqlerrm;
    dbms_output.put_line(sqlerrm);
  when others then
    retcd:=sqlcode;
    errmsg:=sqlerrm;
    dbms_output.put_line(sqlerrm);
  end;
begin
  for i in 1..10 loop
    region_ids(i):=i;
    region_names(i):=‘region‘||i;
  end loop;
  load_regions_bulk_bind(region_ids,region_names,ret_code,ret_errmsg);
  forall i in 1..10 save exceptions update region_tab set region_name=‘N_‘||region_name where region_id=region_ids(i) returning region_name bulk collect into out_region_names;
  --将update操作的新值用retunrning子句返回,返回的方式以批的方式返回,以集合为载体
  commit;
  for idx in out_region_names.first..out_region_names.last loop
    dbms_output.put_line(out_region_names(idx));
  end loop;
exception when others then
  dbms_output.put_line(sqlerrm);
end;

N_region1
N_region2
N_region3
N_region4
N_region5
N_region6
N_region7
N_region8
N_region9
N_region10

(4) 在异常处理部分得到被拒绝的数据:

declare
  type region_id_tb1 is table of number index by binary_integer;
  type region_name_tb1 is table of varchar2(100) index by binary_integer;
  region_ids region_id_tb1;
  region_names region_name_tb1;
  ret_code number;
  ret_errmsg varchar2(200);
  out_region_names region_name_tb1;
  procedure load_regions_bulk_bind(region_ids in region_id_tb1,region_names in region_name_tb1,retcd out number, errmsg out varchar2) is
    bulk_bind_excep exception;
    pragma exception_init(bulk_bind_excep,-24381);
  begin
    delete from region_tab;
    commit;
    forall i in region_ids.first..region_ids.last save exceptions insert into region_tab values(region_ids(i),region_names(i));
    commit;
    retcd:=0;
    errmsg:=‘successful!‘;
  exception when bulk_bind_excep then
    for i in 1..sql%bulk_exceptions.count loop
      dbms_output.put_line(to_char(sql%bulk_exceptions(i).error_index));
      dbms_output.put_line(sqlerrm(-sql%bulk_exceptions(i).error_code));
    end loop;
    for i in 1..region_ids.count loop
      if sql%bulk_rowcount(i)>0 then
        dbms_output.put_line(to_char(sql%bulk_rowcount(i)));
      elsif sql%bulk_rowcount(i)=0 then
        dbms_output.put_line(‘NO ROWS‘);
        dbms_output.put_line(to_char(region_ids(i)));  
        dbms_output.put_line(region_names(i));
      end if;
    end loop;
    dbms_output.put_line(to_char(sql%rowcount));
    retcd:=sqlcode;
    errmsg:=sqlerrm;
    dbms_output.put_line(sqlerrm);
  when others then
    retcd:=sqlcode;
    errmsg:=sqlerrm;
    dbms_output.put_line(sqlerrm);
  end;
begin
  for i in 1..10 loop
    region_ids(i):=i;
    region_names(i):=‘region‘||i;
  end loop;
  region_names(3):=‘very very very very very very very very long‘;
  region_names(6):=‘very very very very very very very very short‘;
  load_regions_bulk_bind(region_ids,region_names,ret_code,ret_errmsg);
  forall i in 1..10 save exceptions update region_tab set region_name=‘N_‘||region_name where region_id=region_ids(i) returning region_name bulk collect into out_region_names;
  --将update操作的新值用retunrning子句返回,返回的方式以批的方式返回,以集合为载体
  commit;
  for idx in out_region_names.first..out_region_names.last loop
    dbms_output.put_line(out_region_names(idx));
  end loop;
exception when others then
  dbms_output.put_line(sqlerrm);
end;


输出:
3
ORA-12899: value too large for column  (actual: , maximum: )
6
ORA-12899: value too large for column  (actual: , maximum: )
1
1
NO ROWS
3
very very very very very very very very long
1
1
NO ROWS
6
very very very very very very very very short
1
1
1
1
8
ORA-24381: error(s) in array DML
N_region1
N_region2
N_region4
N_region5
N_region7
N_region8
N_region9
N_region10

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。