PLSQL批处理语句(BULKCOLLECT⼦句和FORALL语句)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 lect * from ur_objects;
现在需要从my_objects表提取所有数据,我们通常的做法如下:
--FOR游标提取据
世界的物质统一性
declare
type nt_object is table of my_objects%rowtype;
vnt_object nt_object := nt_object(); --初始化
v_count number := 0;
c_big_number number := power(2, 31);
l_start_time pls_integer;
begin
dbms_output.put_line('========FOR游标提取==========');
l_start_time := _time;
for vrt_object in (lect * from my_objects) loop
真心话
d;
vnt_object(vnt_object.last) := vrt_object;
end loop;
dbms_output.put_line('count=' || unt);
dbms_output.put_line('Elapd: ' ||
to_char(mod(_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
lect * from my_objects;
vrt_object cur_object%rowtype;
begin
dbms_output.put_line('========显⽰游标提取==========');
深化在线l_start_time := _time;
open cur_object;
loop
fetch cur_object
into vrt_object;
exit when cur_object%notfound;
d;
vnt_object(vnt_object.last) := vrt_object;
end loop;
clo cur_object;
dbms_output.put_line('count=' || unt);
dbms_output.put_line('Elapd: ' ||
to_char(mod(_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语句中使⽤BULK COLLECT语句。
如果BULK COLLECT没有找到任何⾏,不会抛出NO_DATA_FOUND异常。相反,我们必须对集合的内容进⾏检查看看其中到底有没有数据。
如果查询没有返回任何⾏,集合的COUNT⽅法将返回0。
1.1使⽤隐式游标
使⽤隐式游标(SELECT INTO)重写,并使⽤_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 := _time;
lect * bulk collect into vnt_object from my_objects;
dbms_output.put_line('count=' || unt);
dbms_output.put_line('Elapd: ' ||
to_char(mod(_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
lect * from my_objects;
begin
dbms_output.put_line('========显⽰游标BULK COLLECT提取==========');
l_start_time := _time;
open cur_object;
fetch cur_object bulk collect
into vnt_object;
clo cur_object;
dbms_output.put_line('count=' || unt);
dbms_output.put_line('Elapd: ' ||
to_char(mod(_time - l_start_time +
c_big_number,
c_big_number)));
end;
1.3限制BULK COLLECT提取数据
Oracle为BULK COLLECT提供了⼀个LIMIT⼦句,让我们可以对从数据库提取的⾏的数量做限制,语法是:
FETCH cursor BULK COLLECT INTO ... [LIMIT rows]
其中rows可是直接量、变量或者求值的结果是整数的表达式。
对于BULK COLLECT来说,LIMIT是⾮常有⽤的,因为这个语句可以帮助我们控制程序⽤多⼤内存来处理数据。⽐如,假设你需要查询并处理10000⾏的数据。你可以⽤BULK COLLECT⼀次取出所有的⾏,然后填充到⼀个⾮常⼤的集合中。可是,这种⽅法会消耗掉该会话的⼤量PGA内存。如果这个代码被多个Oracle 模式运⾏,你的应⽤程序性能就可能会因为PGA换页⽽下降。
declare
type nt_object is table of my_objects%rowtype;
vnt_object_bulk nt_object;
vnt_object nt_object := nt_object(); --初始化
c_big_number number := power(2, 31);
l_start_time pls_integer;
cursor cur_object is
lect * from my_objects;
begin
dbms_output.put_line('========显⽰游标BULK COLLECT LIMIT提取==========');
l_start_time := _time;
open cur_object;
loop
fetch cur_object bulk collect
into vnt_object_bulk limit 100;
for i in vnt_object_bulk.first .. vnt_object_bulk.last loop
d;
vnt_object(vnt_object.last) := vnt_object_bulk(i);
end loop;
exit when cur_object%notfound;
end loop;
clo cur_object;
dbms_output.put_line('count=' || unt);
dbms_output.put_line('Elapd: ' ||
赵世炎to_char(mod(_time - l_start_time +
c_big_number,
c_big_number)));
end;
注意:这⾥是在循环的最后通过检查cur_object%notfound的值来结束循环。当每次只查询⼀条数据时,总是把这个代码紧跟在FETCH语句的后⾯。不过使⽤BULK COLLECT时就不能这么做了,因为当FETCH操作提取最后⼀部分数据集之后,游标虽然空了(%NOTFOUND会返回TRUE)但是在集合中还有⼀些元素需要处理。因此,或者在循环的最后检查%NOTFOUND属性,或者在FETCH操作之后⽴即查看集合的内容:
open cur_object;
loop
fetch cur_object bulk collect
into vnt_object_bulk limit 100;
exit when vnt_unt = 0;
和在循环体的最后检查%NOTFOUND属性值⽐较起来,第⼆中⽅法的不好之处就在于我们需要额外再执⾏⼀个返回空⾏的FETCH操作。
2.通过FORALL加速DML
BULK COLLECT⽤于对查询加速。⽽FORALL会对插⼊、更新、删除以及合并做同样的事情(只有Oracle 11g才⽀持FORALL的合并)。FORALL告诉PL/SQL 引擎要先把⼀个或者多个集合的所有成员都绑定到SQL语句中,然后在把语句发送给SQL引擎。
2.1FORALL语句的语法
尽管FORALL语句带有⼀个迭代模式,但它并不是⼀个FOR循环。因此,既不需要LOOP也不需要END LOOP语句。它的语法如下:
FORALL index IN
[lower.bound .. upper.bound |
INDICES OF indexing_collection |
VALUES OF indexing_collection
]
[SAVE EXCEPTIONS]
sql_statement;
其中:
index
是⼀个整数,由Oracle隐式声明的,并被定义做集合的索引值。
lower_bound
新学期的心愿
操作开始的索引值。
upper_bound
操作结束的索引值。
sql_statement
将对每⼀个集合元素执⾏的SQL语句。
indexing_collection
这是⼀个PL/SQL集合,是⼀个指向sql_statement所使⽤的绑定数组的索引的集合。INDICES OF和VALUES OF是从Oracle 10g才有的。
SAVE EXCEPTIONS
这是⼀个可选的⼦句,告诉FORALL处理全部⾏,不过把发⽣的任何异常保存下来。
使⽤FORALL时,必须遵守这些规则:
FORALL语句的主体必须是⼀个单独的DML语句——可以是⼀个插⼊、更新、删除或者合并操作(Oracle 11g及以后版本)。
上边界和下边界对于SQL语句所使⽤的集合来说,必须是⼀个有效的连续索引值范围。
DML语句中使⽤的集合下标不能是表达式。
2.2FORALL批量插⼊
从ur_objects数据字典中中批量将所有数据插⼊到my_objects表中。