Oracle SQL 学习笔记31 - Oracle优化

PL/SQL代码优化

可以从以下几个方面优化PL/SQL

  1. 写相似或相同的代码以便于复用
  2. 在SQL和PLSQL之间做出正确选择
  3. 恰当利用Bulk Binding
  4. 注意条件判断的顺序
  5. 注意数据类型和常量

代码模块化

  • 子程序不超过一页或者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引擎之间的上下文切换。


bulk_bind.JPG

将数组所有的值绑定,而不是循环的进行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;

识别和调试内存问题

m_issue.JPG

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。

  1. 当向PL/SQL传入host游标变量的时候使用OPEN-FOR语句
  2. 适当的条件下使用客户端PL/SQL
  3. 避免不需要的reparsing,例如反复执行的动态SQL可以考虑dbms_sql.parse。
  4. 使用数组处理
  5. 使用表函数来提高性能,以减少函数一次处理的数据量
  6. 适当的条件下使用RETURNING子句

本地编译优化

native_interpreted.JPG

你可能感兴趣的:(Oracle SQL 学习笔记31 - Oracle优化)