PL/SQL批处理语句(BULK COLLECT子句和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 select * from user_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 := 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_time := dbms_utility.get_time;
open cur_object;
fetch cur_object bulk collect
into vnt_object;
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;
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
select * from my_objects;
begin
dbms_output.put_line('========显示游标BULK COLLECT LIMIT提取==========');
l_start_time := dbms_utility.get_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
vnt_object.extend;
vnt_object(vnt_object.last) := vnt_object_bulk(i);
end loop;
exit when cur_object%notfound;
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;
注意:这里是在循环的最后通过检查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_object_bulk.count = 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批量插入
从user_objects数据字典中中批量将所有数据插入到my_objects表中。
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('========批量插入==========');
l_start_time := dbms_utility.get_time;
select * bulk collect into vnt_object from user_objects;
forall i in vnt_object.first .. vnt_object.last
insert into my_objects values vnt_object (i);
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;
提示:在Oracle 10g及以后的版本中,PL/SQL编辑会自动对FOR游标循环进行优化,从而性能可以和BULK COLLECT相媲美。
参考:
Oracle PL/SQL程序设计(第五版) Steven Feuersterin & Bill Pribyl著 张晓明译
PL/SQL批处理语句(BULK COLLECT子句和FORALL语句)的更多相关文章
- PL/SQL批处理语句(一)BULK COLLECT
我们知道PL/SQL程序中运行SQL语句是存在开销的,因为SQL语句是要提交给SQL引擎处理,这种在PL/SQL引擎和SQL引擎之间的控制转移叫做上下文却换,每次却换时,都有额外的开销.然而,FORA ...
- PL/SQL批处理语句(二)FORALL
PL/SQL批处理语句(二)FORALL 我们知道PL/SQL程序中运行SQL语句是存在开销的,因为SQL语句是要提交给SQL引擎处理,这种在PL/SQL引擎和SQL引擎之间的控制转移叫做上下文却换, ...
- Oracle批量SQL之 BULK COLLECT 子句
BULK COLLECT 子句会批量检索结果,即一次性将结果集绑定到一个集合变量中,并从SQL引擎发送到PL/SQL引擎.通常可以在SELECT INTO.FETCH INTO以及RETURNING ...
- 【SQL Server学习笔记】Delete 语句、Output 子句、Merge语句
原文:[SQL Server学习笔记]Delete 语句.Output 子句.Merge语句 DELETE语句 --建表 select * into distribution from sys.obj ...
- PL/SQL学习(二)条件和循环语句
原文参考:http://plsql-tutorial.com/ PLSQL条件语句 IF THEN ELSE STATEMENT 1) IF condition THEN statement ...
- PL/SQL详细介绍,设置oracle相关
1. 实现参照完整性 指若两个表之间具有主从关系(即主外键关系),当删除主表数据时,必须确保相关的从表数据已经被删除. 当修改主表的主键列数据时,必须确保相关从表数据已经被修改.为了实现级 ...
- 转://Oracle PL/SQL 优化与调整 -- Bulk 说明
一. Bulk 概述 本来只想测试一下Bulk Collect 和update性能的,但发现Bulk 的东西还是很多的,在OTN上搜了一些,整理如下. 1.1 Bulk Binding 和 Bulk ...
- PL/SQL 训练13--plsql 优化
--数据缓存技术 --PGA和SGA---SGA:系统全局区域--PGA:Process Global Area是为每个连接到Oracle的用户进程保留的内存. ---PLSQL从PGA获取信息的速度 ...
- PL/SQL 批量SQL
批量SQL包括: FORALL语句 BULK COLLECT子句 FORALL语句 FORALL具有如下结构: FORALL loop_counter IN bounds_clause [SAVE E ...
随机推荐
- 设计模式 ( 十五 ) 中介者模式Mediator(对象行为型)
设计模式 ( 十五 ) 中介者模式Mediator(对象行为型) 1.概述 在面向对象的软件设计与开发过程中,根据“单一职责原则”,我们应该尽量将对象细化,使其只负责或呈现单一的职责,即将行为分布到各 ...
- stm32之ADC
将模拟量转换为数字量的过程称为模式(A/D)转换,完成这一转换的期间成为模数转换器(简称ADC);将数字量转换为模拟量的过程为数模(D/A)转换,完成这一转换的器件称为数模转换器(简称DAC). 模拟 ...
- HDU1005(周期问题)
Description A number sequence is defined as follows: f(1) = 1, f(2) = 1, f(n) = (A * f(n - 1) + B * ...
- OJ双人赛:程序设计竞赛的新尝试
早就想在所教的班上组织一次程序设计竞赛,直到冒出双人赛形式的念头.出题.分组.竞赛,又是一次新的尝试. 做为在教学环节中组织的竞赛,不同于自愿报名的竞赛,必须全员参与.享受比赛要追求,培养团队意识也要 ...
- IOS 调用系统相册或照相机tab按钮显示中文
- initialize和init区别
Objective-C很有趣的一个地方是,它非常非常像C.实际上,它就是C语言加上一些其他扩展和一个运行时间(runtime). 有了这个在每个Objective-C程序中都会起作用的附加运行时间,给 ...
- 转:Android -- ActivityLifeCycleCallbacks
http://www.cnblogs.com/yydcdut/p/4945990.html 一个不常见类的使用,名字叫ActivityLifeCycleCallbacks 通常一个项目中有很多个act ...
- Docker学习笔记(4) — 开启Docker远程访问
默认情况下,Docker守护进程会生成一个socket(/var/run/docker.sock)文件来进程本地进程通信,而不会监听任何端口,因此只能在本地使用docker客户端或者使用Docker ...
- 每天4个linux命令--步骤一
1 :Linux的诞生 Linux由芬兰赫尔辛基大学的Linus Torvalds创建 1991年10月,Linux第一个公开版 0.02版发布 1994年3月,Linux 1.0版发布 Linus ...
- C语言,变量与内存
一.数在计算机中的二进制表示 符号位:最高位为符号位,正数该位为0,负数该位为1: 原码:原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值 反码:正数的反码是其本身:负数的反码是在 ...