Oracle为PL/SQL中的SQL相关功能提供了FORALL语句和BULK COLLECT子句,显著的增强了SQL相关功能。这两个语句一起被称作PL/SQL的批处理语句。Oracle为什么要提供这两个语句呢?我们首先了解一下PL/SQL的引擎。该引擎可以安装在数据库,或者应用开发工具上,例如Oracle Froms。当PL/SQL运行引擎执行一个代码块时,引擎本身只会处理过程语句,而SQL语句是发送给SQL引擎执行。SQL语句的执行时是由数据库的SQL引擎负责,再将执行结果返回给PL/SQL引擎。
以下是PL/SQL引擎运行原理:

这种PL/SQL引擎和SQL引擎之间的控制转移叫做上下文切换。每次发生切换时,都会有额外的开销。通过FORALL语句和BULK COLLECT子句,可以把两个引擎的通行进行微调,让PL/SQL更有效地把多个上下文切换压缩成一个切换,从而提升程序的性能。
1.通过BULK COLLECT加速查询
不管是显示游标还是隐式游标,都可以通过BULK COLLECT在数据库的单次交互中获取多行数据。BULKCOLLECT减少了PL/SQL引擎和SQL引擎之间的切换次数,因此也减少了提取数据时的额外开销。
创建一张测试数据表:create table my_objects as select * from user_objects;< http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+z9bU2tDo0qq00215X29iamVjdHOx7czhyKHL+dPQyv2+3SzO0sPHzaizo7XE1/a3qMjnz8KjujwvcD4KPHA+LS1GT1LTzrHqzOHIob7dPC9wPgo8cD5kZWNsYXJlPGJyPgogIHR5cGUgbnRfb2JqZWN0IGlzIHRhYmxlIG9mIG15X29iamVjdHMlcm93dHlwZTs8YnI+CiAgdm50X29iamVjdCAgIG50X29iamVjdCA6PSBudF9vYmplY3QoKTsgLS2z9cq8u688YnI+CiAgdl9jb3VudCAgICAgIG51bWJlciA6PSAwOzxicj4KICBjX2JpZ19udW1iZXIgbnVtYmVyIDo9IHBvd2VyKDIsIDMxKTs8YnI+CiAgbF9zdGFydF90aW1lIHBsc19pbnRlZ2VyOzxicj4KYmVnaW48YnI+CiAgZGJtc19vdXRwdXQucHV0X2xpbmUo"========FOR游标提取==========');
l_start_time := dbms_utility.get_time;
for vrt_object in (select * from my_objects) loop
vnt_object.extend;
vnt_object(vnt_object.last) := vrt_object;
end loop;
dbms_output.put_line('count=' || vnt_object.count);
dbms_output.put_line('Elapsed: ' ||
to_char(mod(dbms_utility.get_time - l_start_time +
c_big_number,
c_big_number)));
end;
--显示游标提取
declare
type nt_object is table of my_objects%rowtype;
vnt_object nt_object := nt_object(); --初始化
c_big_number number := power(2, 31);
l_start_time pls_integer;
cursor cur_object is
select * from my_objects;
vrt_object cur_object%rowtype;
begin
dbms_output.put_line('========显示游标提取==========');
l_start_time := dbms_utility.get_time;
open cur_object;
loop
fetch cur_object
into vrt_object;
exit when cur_object%notfound;
vnt_object.extend;
vnt_object(vnt_object.last) := vrt_object;
end loop;
close cur_object;
dbms_output.put_line('count=' || vnt_object.count);
dbms_output.put_line('Elapsed: ' ||
to_char(mod(dbms_utility.get_time - l_start_time +
c_big_number,
c_big_number)));
end;
结果:FOR游标明显要优于显示游标
注意:要使用集合嵌套表,必须初始化。
这个代码毫无疑问可以完成任务,不过可能会花费很长的时间。假设my_objects表中有1000个记录,PL/SQL引擎就要向SGA中的游标发送10000个fetch操作。
为了帮组这种场景,可以在查询语句中的INTO元素中使用BULK COLLECT子句。对于游标使用这个子句是告诉SQL引擎把查询出来的多行数据库批量绑定到指定的集合上。然后再把控制返回给PL/SQL引擎。这个子句的语法是:
... BULK COLLECT INTO collection_name[,collection_name] ...
其中collection_name代表一个集合。
使用BULK COLLECT时,要记住以下这些规则和限制:
在 Oracle 9i数据之前,只能在静态SQL中使用BULK COLLECT。现在不论是动态还是静态SQL都可以使用BULK COLLECT。可以在下面这些语句中使用BULK COLLECT:SELECT INTO,FETCH INTO和RETURNING INTO。对于在BULK COLLECT子句中使用的集合,SQL引擎会自动进行初始化及扩展。它会从索引1开始填充集合,连续的插入元素(紧凑的),把之前已经使用的元素的值覆盖。不能在FORALL语句中使用SELECT...BULK COLLECT语句。如果SELECT...BULK COLLECT没有找到任何行,不会抛出NO_DATA_FOUND异常。相反,我们必须对集合的内容进行检查看看其中到底有没有数据。如果查询没有返回任何行,集合的COUNT方法将返回0。1.1使用隐式游标
使用隐式游标(SELECT INTO)重写,并使用dbms_utility.get_time获取时间。
declare
type nt_object is table of my_objects%rowtype;
vnt_object nt_object; --未初始化
c_big_number number := power(2, 31);
l_start_time pls_integer;
begin
dbms_output.put_line('========BULK COLLECT批量提取==========');
l_start_time := dbms_utility.get_time;
select * bulk collect into vnt_object from my_objects;
dbms_output.put_line('count=' || vnt_object.count);
dbms_output.put_line('Elapsed: ' ||
to_char(mod(dbms_utility.get_time - l_start_time +
c_big_number,
c_big_number)));
end;
1.2使用显示游标
使用显示游标重写:
declare
type nt_object is table of my_objects%rowtype;
vnt_object nt_object := nt_object(); --初始化
c_big_number number := power(2, 31);
l_start_time pls_integer;
cursor cur_object is
select * from my_objects;
begin
dbms_output.put_line('========显示游标BULK COLLECT提取==========');
l_start_tim