PL/SQL块的执行过程
当PL/SQL运行时引擎处理一块代码时,它使用PL/SQL引擎来执行过程化的代码,而将SQL语句发送给SQL引擎来执行。SQL引擎执行完毕后,将结果再返回给PL/SQL引擎。这种在PL/SQL引擎和SQL引擎之间的交互,称为上下文交换(context switch)。每发生一次交换,就会带来一定的额外开销。

PL/SQL与SQL引擎之间的交换
上图摘自Oracle官网,形象的说明了上述执行过程。有兴趣的同学可以参考Oracle官网对FORALL与BULK COLLECT的详细说明。地址如下:
http://www.oracle.com/technetwork/issue-archive/2012/12-sep/o52plsql-1709862.html

2 FORALL和BULK COLLECT特点
这两个语句在PL/SQL内部进行一种数组处理,BULK COLLECT提供对数据的高速检索,FORALL可大大改进INSERT、UPDATE和DELETE操作的性能。Oracle数据库使用这些语句大大减少了PL/SQL与SQL语句执行引擎的环境切换次数,从而使其性能有了显著提高。

FORALL,用于增强PL/SQL引擎到SQL引擎的交换。
BULK COLLECT,用于增强SQL引擎到PL/SQL引擎的交换。
如果你要插入5000条数据,一般情况下,在pl/sql中用for循环,循环插入5000次,
而用forall一次就可以插入5000条,提高了性能和速度。
3 FORALL介绍
使用FORALL,可以将多个DML批量发送给SQL引擎来执行,最大限度地减少上下文交互所带来的开销。

3.1 FORALL语法
FORALL index_name IN
{ lower_bound … upper_bound
| INDICES OF collection_name [ BETWEEN lower_bound AND upper_bound ]
| VALUES OF index_collection
}
[ SAVE EXCEPTIONS ] dml_statement;
说明:

index_name:一个无需声明的标识符,作为集合下标使用。
lower_bound … upper_bound:数字表达式,来指定一组连续有效的索引数字下限和上限。该表达式只需解析一次。
INDICES OF collection_name:用于指向稀疏数组的实际下标。跳过没有赋值的元素,例如被 DELETE 的元素,NULL 也算值。
VALUES OF index_collection_name:把该集合中的值当作下标,且该集合值的类型只能是 PLS_INTEGER/BINARY_INTEGER。
SAVE EXCEPTIONS:可选关键字,表示即使一些DML语句失败,直到FORALL LOOP执行完毕才抛出异常。可以使用SQL%BULK_EXCEPTIONS 查看异常信息。
dml_statement:静态语句,例如:UPDATE或者DELETE;或者动态(EXECUTE IMMEDIATE)DML语句。
3.2 FORALL案例
见sqlscripts/forall-bulkcollect包下的sql脚本事例(所有事例代码见文末的github地址)。

3.3 FORALL注意事项
使用FORALL时,应该遵循如下规则:

FORALL语句的执行体,必须是一个单独的DML语句,比如INSERT,UPDATE或DELETE。
不要显式定义index_row,它被PL/SQL引擎隐式定义为PLS_INTEGER类型,并且它的作用域也仅仅是FORALL。
这个DML语句必须与一个集合的元素相关,并且使用FORALL中的index_row来索引。注意不要因为index_row导致集合下标越界。
lower_bound和upper_bound之间是按照步进 1 来递增的。
在sql_statement中,不能单独地引用集合中的元素,只能批量地使用集合。
在sql_statement中使用的集合,下标不能使用表达式。
a. insert into test2 values dr_table(i);dbms_output.put_line(i);
不正确,找不到i,因为forall中只能使用单条语句可以引用索引变量。
b. insert into test2 values(dr_table(i).id,dr_table(i).name);
集合的field不可以在forall中使用,必须是整体使用
c. insert into test2 values dr_table(i+1);
错误,不可以对索引变量进行运算
d. insert into test2 values(dr_table(i));
报没有足够的值错误,此处外面不可以加括号,当有多个字段的时候,单个字段可以加括号
4 BULK COLLECT的使用
4.1 在SELECT INTO中使用BULK COLLECT
DECLARE
– 定义记录类型
TYPE EMP_REC_TYPE IS RECORD(
EMPNO EMP.EMPNO%TYPE,
ENAME EMP.ENAME%TYPE,
HIREDATE EMP.HIREDATE%TYPE);
– 定义基于记录的嵌套表
TYPE NESTED_EMP_TYPE IS TABLE OF EMP_REC_TYPE;
– 声明变量
EMP_TAB NESTED_EMP_TYPE;
BEGIN
– 使用BULK COLLECT将所得的结果集一次性绑定到记录变量emp_tab中
SELECT EMPNO, ENAME, HIREDATE BULK COLLECT INTO EMP_TAB FROM EMP;

FOR I IN EMP_TAB.FIRST … EMP_TAB.LAST LOOP
DBMS_OUTPUT.PUT_LINE('当前记录: ’ || EMP_TAB(I)
.EMPNO || CHR(9) || EMP_TAB(I)
.ENAME || CHR(9) || EMP_TAB(I).HIREDATE);
END LOOP;
END;
说明:使用BULK COLLECT一次即可提取所有行并绑定到记录变量,这就是所谓的批量绑定。

4.2 在FETCH INTO中使用BULK COLLECT
在游标中可以使用BLUK COLLECT一次取出一个数据集合,比用游标单条取数据效率高,尤其是在网络不大好的情况下。

语法:

FETCH … BULK COLLECT INTO …[LIMIT row_number];
注意:

在使用BULK COLLECT子句时,对于集合类型会自动对其进行初始化以及扩展。因此如果使用BULK COLLECT子句操作集合,则无需对集合进行初始化以及扩展。
由于BULK COLLECT的批量特性,如果数据量较大,而集合在此时又自动扩展,为避免过大的数据集造成性能下降,因此可以使用LIMIT子句来限制一次提取的数据量。
LIMIT子句只允许出现在FETCH操作语句的批量中.

DECLARE
CURSOR EMP_CUR IS
SELECT EMPNO, ENAME, HIREDATE FROM EMP;

TYPE EMP_REC_TYPE IS RECORD(
EMPNO EMP.EMPNO%TYPE,
ENAME EMP.ENAME%TYPE,
HIREDATE EMP.HIREDATE%TYPE);
– 定义基于记录的嵌套表
TYPE NESTED_EMP_TYPE IS TABLE OF EMP_REC_TYPE;
– 声明集合变量
EMP_TAB NESTED_EMP_TYPE;
– 定义了一个变量来作为limit的值
V_LIMIT PLS_INTEGER := 5;
– 定义变量来记录FETCH次数
V_COUNTER PLS_INTEGER := 0;
BEGIN
OPEN EMP_CUR;

LOOP
– fetch时使用了BULK COLLECT子句
FETCH EMP_CUR BULK COLLECT
INTO EMP_TAB LIMIT V_LIMIT; – 使用limit子句限制提取数据量

EXIT WHEN EMP_TAB.COUNT = 0; -- 注意此时游标退出使用了emp_tab.COUNT,而不是emp_cur%notfound
V_COUNTER := V_COUNTER + 1; -- 记录使用LIMIT之后fetch的次数FOR I IN EMP_TAB.FIRST .. EMP_TAB.LAST LOOPDBMS_OUTPUT.PUT_LINE('当前记录: ' || EMP_TAB(I).EMPNO || CHR(9) || EMP_TAB(I).ENAME || CHR(9) || EMP_TAB(I).HIREDATE);
END LOOP;

END LOOP;

CLOSE EMP_CUR;

DBMS_OUTPUT.PUT_LINE(‘总共获取次数为:’ || V_COUNTER);
END;
4.3 在RETURNING INTO中使用BULK COLLECT
BULK COLLECT除了与SELECT,FETCH进行批量绑定之外,还可以与INSERT,DELETE,UPDATE语句结合使用。
当与这几个DML语句结合时,需要使用RETURNING子句来实现批量绑定。

DECLARE
TYPE EMP_REC_TYPE IS RECORD(
EMPNO EMP.EMPNO%TYPE,
ENAME EMP.ENAME%TYPE,
HIREDATE EMP.HIREDATE%TYPE);
TYPE NESTED_EMP_TYPE IS TABLE OF EMP_REC_TYPE;
EMP_TAB NESTED_EMP_TYPE;
BEGIN
DELETE FROM EMP
WHERE DEPTNO = 20 RETURNING EMPNO, ENAME, HIREDATE – 使用returning 返回这几个列
BULK COLLECT INTO EMP_TAB; – 将返回的列的数据批量插入到集合变量

DBMS_OUTPUT.PUT_LINE(‘删除 ’ || SQL%ROWCOUNT || ’ 行记录’);
COMMIT;

IF EMP_TAB.COUNT > 0 THEN
– 当集合变量不为空时,输出所有被删除的元素
FOR I IN EMP_TAB.FIRST … EMP_TAB.LAST LOOP
DBMS_OUTPUT.PUT_LINE(‘当前记录:’ || EMP_TAB(I)
.EMPNO || CHR(9) || EMP_TAB(I)
.ENAME || CHR(9) || EMP_TAB(I)
.HIREDATE || ’ 已被删除’);
END LOOP;
END IF;
END;
4.4 BULK COLLECT的注意事项
BULK COLLECT INTO 的目标对象必须是集合类型。
只能在服务器端的程序中使用BULK COLLECT,如果在客户端使用,就会产生一个不支持这个特性的错误。
不能对使用字符串类型作键的关联数组使用BULK COLLECT子句。
复合目标(如对象类型)不能在RETURNING INTO子句中使用。
如果有多个隐式的数据类型转换的情况存在,多重复合目标就不能在BULK COLLECT INTO子句中使用。
如果有一个隐式的数据类型转换,复合目标的集合(如对象类型集合)就不能用于BULK COLLECTINTO子句中
5 FORALL与BULK COLLECT综合运用
FORALL与BULK COLLECT是实现批量SQL的两个重要方式,我们可以将其结合使用以提高性能.

– create tb_emp_test
CREATE TABLE tb_emp_test AS
SELECT empno, ename, hiredate
FROM EMP_TEST
WHERE 1 = 0;

DECLARE
– declare cursor
CURSOR EMP_CUR IS
SELECT EMPNO, ENAME, HIREDATE FROM EMP_TEST;
– 基于游标的嵌套表类型
TYPE NESTED_EMP_TYPE IS TABLE OF EMP_CUR%ROWTYPE;
– 声明变量
EMP_TAB NESTED_EMP_TYPE;
BEGIN
SELECT EMPNO, ENAME, HIREDATE BULK COLLECT
INTO EMP_TAB
FROM EMP_TEST
WHERE SAL > 1000;

– 使用FORALL语句将变量中的数据插入到表tb_emp
FORALL I IN 1 … EMP_TAB.COUNT
INSERT INTO
(SELECT EMPNO, ENAME, HIREDATE FROM TB_EMP_TEST)
VALUES EMP_TAB
(I);

COMMIT;
DBMS_OUTPUT.PUT_LINE('总共向 tb_emp 表中插入记录数: ’ || EMP_TAB.COUNT);
END;
6 总结
limit减少内存占用,如果数据量较大一次性全部加载到内存中,对PGA来说压力太大,可采用limit的方法一次加载一定数量的数据,建议值通常为1000。使用limit时注意,循环的时候如果用while cursor_name%found loop,对于最后一次fetch的数据量不足设定值1000,%found条件就会不成立。示例使用v_oid_lst.count > 0 作为判断条件。
在写plsql代码块,定义数值变量时,建议采用pls_integer类型,或者simple_integer类型。两者的区别:
Oracle9i之前有binary_integer类型,和11g中引入的pls_integer数值范围相同:-2147483647~+2147483647,但pls_integer有更高的性能。两者性能均优于number类型。
Oracle中也引入了simple_integer类型,不过不能包含null值,范围:-2147483648~2147483647,性能优于pls_integer。
使用ref cursor。
使用绑定变量。
自定义table类型。
Bulk collect into加载到内存中,处理完业务逻辑后forall批量插入到数据表中。
Forall可以使用returning bulk collect into,且可使用sql%rowcount返回其更新行数。
type numbers is table of number index by binary_integer/pls_integer/simple_integer; 其作用是:
a. 加了"index by binary_integer “后,numbers类型的下标就是自增长,numbers类型在插入元素时,不需要初始化,不需要每次extend增加一个空间。
b. 如果没有这句话"index by binary_integer”,那就得要显示对初始化,且每插入一个元素到numbers类型的table中时,都需要先extend。
事例代码地址:
https://github.com/landy8530/database-example/tree/master/oracle/sqlscripts

作者:landy8530
链接:https://www.jianshu.com/p/4f06b943ff73
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Oracle之FORALL与BULK COLLECT简介(转载)相关推荐

  1. oracle中bulk,Oracle数据库之FORALL与BULK COLLECT语句

    1 PL/SQL块的执行过程 当PL/SQL运行时引擎处理一块代码时,它使用PL/SQL引擎来执行过程化的代码,而将SQL语句发送给SQL引擎来执行: SQL引擎执行完毕后,将结果再返回给PL/SQL ...

  2. Oracle 中使用 fetch bulk collect into 批量效率的读取游标数据

    通常我们获取游标数据是用 fetch some_cursor into var1, var2 的形式,当游标中的记录数不多时不打紧.然而自 Oracle 8i 起,Oracle 为我们提供了 fetc ...

  3. Oracle 中使用 fetch bulk collect into 批量效率的读取

    http://www.jzxue.com/shujuku/oracle/201109/21-8976.html 通常我们获取游标数据是用 fetch some_cursor into var1, va ...

  4. bulk怎么使用oracle,oracle学习之bulk collect用法

    通过bulk collect减少loop处理的开销,使用Bulk Collect提高Oracle查询效率 Oracle8i中首次引入了Bulk Collect特性,该特性可以让我们在PL/SQL中能使 ...

  5. PL/SQL批处理语句:BULK COLLECT 和 FORALL

    PL/SQL程序中运行SQL语句是存在开销的,因为SQL语句是要提交给SQL引擎处理,这种在PL/SQL引擎和SQL引擎之间的控制转移叫做上下文却换,每次却换时,都有额外的开销        请看下图 ...

  6. PostgreSQL Oracle 兼容性之 - PL/SQL FORALL, BULK COLLECT

    Oracle PL/SQL 开发的童鞋,一定对O家的bulk批量处理的性能很是赞赏吧. 但是PostgreSQL用户请不要垂涎,作为学院派和工业界的一颗璀璨明珠. 开源数据库PostgreSQL,也有 ...

  7. forall分批提交oracle,使用BULK COLLECT+FORALL加速批量提交

    一.批量提交 批量提交特点: 占用较少undo,资源(独占锁,undo)快速释放,执行时间长 批量提交适合场景: 在线大批量插入,更新,删除数据 二.BULK COLLECT+FORALL性能提升 1 ...

  8. oracle大数据量迁移,分批量导入样例(fetch...bulk collect)以及forall结合使用

    //插入时不产生日志, alter table IALHospitalInfo nologging; //记录时间 set timing on; declareCURSOR cur is select ...

  9. oracle bulk collect

    碰到的问题,有一个员工表,存储着员工的各种信息. 我要写一个过程,传进去员工的名字,打印该员工的信息. create or replace procedure getInfo(name emp.ena ...

  10. 使用BULK COLLECT+FORALL加速批量提交

    为什么80%的码农都做不了架构师?>>>    一.        批量提交 批量提交特点: 占用较少undo,资源(独占锁,undo)快速释放,执行时间长 批量提交适合场景: 在线 ...

最新文章

  1. 八、pyqt5按钮类控件——QPushButton、QRadioButton、QCheckBox
  2. 【Linux 内核】Linux 内核源码目录说明 ③ ( lib 目录 | LICENSES 目录 | mm 目录 | net 目录 | samples 目录 | scripts 目录 )
  3. Jsoup抓取网页数据完成一个简易的Android新闻APP
  4. Shiro框架:授权流程、授权方式、Shiro授权入门程序、自定义Realm进行授权
  5. mysql 的数据库实例理解_理解数据库和实例
  6. 决策树php,决策树模型组合之随机森林与GBDT
  7. 正则表达式的先行断言(lookahead)和后行断言(lookbehind)
  8. Day11名称空间,作用域,闭包函数
  9. c 语言游戏代码大全,C语言经典游戏代码
  10. 2011年国庆发布:(cppblog)C++博客十八罗汉造像
  11. 真懂?Retrofit完整剖析
  12. VI,NDVI,EVI
  13. 实际经历告诉你,写一本技术书能赚多少钱(转)
  14. 什么是erp办公系统
  15. 硅谷最神奇的成功秘诀《世界上最神奇的24堂课》(查尔斯.哈奈尔, Charles F.Haanel)...
  16. 自媒体淘客选好爆文标题轻松月入过万
  17. 【服务器数据恢复】服务器reiserfs文件系统损坏的数据恢复案例
  18. 用python求数列前n项的平方和
  19. 物联网卡的作用有哪些
  20. Vagrant:vagrant up卡死

热门文章

  1. 测试手机ram速度软件,8GB RAM极限是什么?我们拿两款手机测试了一下
  2. 有return的情况下try catch finally的执行顺序(最有说服力的总结) 后面的神评论
  3. 根据不同时区来计算当前时间
  4. Android R 设置壁纸流程和 Launcher 闪烁问题
  5. HBuilderX 导入uniapp后,无法运行或发行,提示“[微信小程序开发者工具] × open IDE”
  6. P1156 垃圾陷阱(背包dp)
  7. Genesys Business Continuity 部署
  8. 从Trie树(字典树)谈到后缀树(10.28修订)
  9. idea 内存溢出问题
  10. 【Java的Excel操作】MyExcel