PL/SQL代码优化
可以从以下几个方面优化PL/SQL
- 写相似或相同的代码以便于复用
- 在SQL和PLSQL之间做出正确选择
- 恰当利用Bulk Binding
- 注意条件判断的顺序
- 注意数据类型和常量
代码模块化
- 子程序不超过一页或者60行
- 推荐使用包进行代码封装
- 使用本地子程序封装反复操作的逻辑
- 用函数封装运算或反复查询的逻辑
比较SQL和PL/SQL
SQL和PL/SQL都有自己的优点。
SQL以集合的形式访问数据库中的数据;PL/SQL提供了过程化的能力,更加灵活。
- 对于一些简单的集合处理,SQL的速度更快:
BEGIN
INSERT INTO inventories2
SELECT product_id, warehouse_id
FROM main_inventories;
END;
批量绑定优化
可以用批量绑定来减少PL/SQL引擎和SQL引擎之间的上下文切换。
将数组所有的值绑定,而不是循环的进行fetch、insert、update和delete多行。
- 反例
FOR i IN 1 .. 5000 LOOP
INSERT INTO bulk_bind_example_tbl
VALUES(...)
END LOOP;
...
- 正例
FORALL i IN 1 .. 50000
INSERT INTO bulk_bind_example_tbl
VALUES(...);
END;
...
- 正例2
可以用BULK COLLECT 去改善性能
CREATE OR REPLACE PROCEDURE process_customers
(p_account_mgr customers.account_mgr_id%TYPE)
IS
TYPE typ_numtab IS TABLE OF
customers.customer_id%TYPE;
TYPE typ_chartab IS TABLE OF
customers.cust_last_name%TYPE;
TYPE typ_emailtab IS TABLE OF
customers.cust_email%TYPE;
v_custnos typ_numtab;
v_last_names typ_chartab;
v_emails typ_emailtab;
BEGIN
SELECT custmer_id, cust_last_name, cust_email
BULK COLLECT INTO v_custnos, v_last_name, v_email
FROM customers
WHERE account_mgr_id = p_account_mgr;
...
END priocess_customers;
- 正例3,使用RETURNING子句来获得正在被修改的行的信息
DECLARE
TYPE typ_replist IS VARRAY(100) OF NUMBER;
TYPE typ_numlist IS TABLE OF orders.order_total%TYPE;
repids typ_replist := typ_replist(153, 155, 156, 161);
totlist typ_numlist;
c_big_total CONSTANT NUMBER := 60000;
BEGIN
FORALL i IN repids.FIRST .. repids.LAST
UPDATE orders
SET order_total = .95 * order_total
WHERE sales_rep_id = repids(i)
AND order_total > c_big_total
RETURNING order_total BULK COLLECT INTO totlist
END;
使用SAVE EXCEPTIONS
可以在FORALL语句中使用SAVE EXCEPTIONS关键字:
FORALL index IN lower_bound..upper_bound
SAVE EXCEPTIONS
{insert_stmt | update_stmt | delete_stmt}
从而,在执行过程中的异常被保存在%BULK_EXCEPTIONS游标属性中,该属性是一系列两个字段记录的集合:
字段 | 定义 |
---|---|
ERROR_INDEX | 异常被抛出的FORALL语句的iteration |
ERROR_CODE | 相关的Oracle错误码 |
这里的值指向最近一次执行的FORALL语句。
处理FORALL 异常
DECLARE
TYPE NumberList IS TABLE OF NUMBER;
num_tab NumList := NumList(100, 0, 110, 300, 0, 199, 200, 0, 400);
bulk_error s EXCEPTION;
PRAGM EXCEPTION_INIT(bulk_errors, -24381);
BEGIN
FORALL i IN num_tab.FIRST .. num_tab.LAST
SAVE EXCEPTIONS
DELETE FROM orders WHERE order_total < 500000/num_tab(i);
EXCEPTION WHEN bulk_errors THEN
DBMS_OUTPUT.PUT_LINE('Number of errors is: ' || SQL%BULK_EXCEPTION.COUNT);
FOR j in i .. SQL%BULK_EXCEPTIONS.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(
TO_CHAR(SQL%BULK_EXCEPTIONS(j).error_index)
|| ' / ' || SQLERRM(SQL%BULK_EXCEPTIONS(j).error_code));
END LOOP;
END;
/
重写条件语句
在逻辑表达式中,只要是表达式的结果确定下来,PL/SQL就会立刻停止对表达式赋值。
- 场景1
IF TRUE|FALSE OR (v_sales_rep_id IS NULL) THEN
...
...
END IF;
- 场景2
IF credit_or(cust_id) AND (v_order_total < 5000) THEN
...
END IF;
如果某个条件的逻辑结果是TRUE,为互斥的子句使用ELSIF,也就是说应该避免:
IF v_acct_mgr = 145 THEN
process_acct_145;
END IF;
IF v_acct_mgr = 147 THEN
process_acct_147;
END IF;
IF v_acct_mgr = 148 THEN
process_acct_148;
END IF;
IF v_acct_mgr = 149 THEN
process_acct_149;
END IF;
而应该用
F v_acct_mgr = 145 THEN
process_acct_145;
ELSIF v_acct_mgr = 147 THEN
process_acct_147;
ELSIF v_acct_mgr = 148 THEN
process_acct_148;
ELSIF v_acct_mgr = 149 THEN
process_acct_149;
END IF;
数据类型和常量
避免隐式的数据类型转换
对于结构上不同的数据类型,PL/SQL将进行隐式的类型转换。比如,当将一个PLS_INTEGER的值赋给一个NUMBER型的变量
DECLARE
n NUMBER;
BEGIN
n := n + 15 --converted
n := n + 15.0 -- not converted
...
END;
为整数使用PLS_INTEGER数据类型
当处理整数的时候,使用PLS_INTEGER数据类型。对于整数来说,PLS_INTEGER更加高效,其占用的存储空间要小于INTEGER和NUMBER,而且它的操作使用的是更为快速的机器算法。
NOT NULL 限制
比较
PROCEDURE calc_m IS
m NUMBER NOT NULL := 0;
a NUMBER;
b NUMBER;
BEGIN
...
m := a + b
...
END;
和
PROCEDURE calc_m IS
m NUMBER;
a NUMBER;
b NUMBER;
BEGIN
...
m := a + b
IF m IS NULL THEN
-- raise error
END IF;
END;
在PL/SQL程序之间传递数据
Pl/SQL非常灵活,可以传递简单的标量数据或者复杂的数据结构。而且,可以使用NOCOPY hint
来提高IN OUT 参数的性能。
将记录作为参数传递,封装了数据,而且减少了代码量:
DECLARE
TYPE CustRec IS RECORD(
customer_id customers.customer_id%TYPE,
cust_last_name VARCHAR2(20),
cust_email VARCHAR2(20),
credit_limit NUMBER(9, 2));
...
PROCEDURE raise_credit(cust_info CustRec);
将collection作为参数
PACKAGE cust_actions IS
TYPE NameTabTyp IS TABLE OF customers.cust_last_name%TYPE
INDEX BY PLS_INTEGER;
TYPE CreditTabTyp IS TABLE OF customers.credit_limit%TYPE
INDEX BY PLS_INTEGER;
PROCEURE credit_batch(name_tab IN NameTabTyp,
credit_tab IN CreditTabTyp)
END cust_actions;
识别和调试内存问题
Pinning 对象
Pinning可以避免使用Oracle LRU(least recently used)机制,而且并不会被flush出内存。Pinning是在sys.dbms_shared_pool包的帮助下实现的:
- sys.dbms_shared_pool.keep
- sys.dbms_shared_pool.unkeep
- sys.dbms_shared_pool.sizes
慎用Pinning。keep procedure在加在pinning 对象之前首先将其放入队列,在实例启动之后,将他们放入连续的内存块。
语法
SYS.DBMS_SHARED_POOL.KEEP(object_name, flag)
SYS.DBMS_SHARED_POOL.UNKEEP(object_name, flag)
实例
...
BEGIN
SYS.DBMS_SHARED_POOL.KEEP('HR.COMM_PKG', 'P')
SYS.DBMS_SHARED_POOL.UNKEEP('OE.OVER_PACK', 'P');
END;
...
网络优化
网络方面的问题也可能会影响PL/SQL。
- 当向PL/SQL传入host游标变量的时候使用OPEN-FOR语句
- 适当的条件下使用客户端PL/SQL
- 避免不需要的reparsing,例如反复执行的动态SQL可以考虑dbms_sql.parse。
- 使用数组处理
- 使用表函数来提高性能,以减少函数一次处理的数据量
- 适当的条件下使用RETURNING子句