美文网首页程序园
Oracle SQL 学习笔记31 - Oracle优化

Oracle SQL 学习笔记31 - Oracle优化

作者: 赵阳_c149 | 来源:发表于2020-02-23 09:35 被阅读0次

    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优化

        本文链接:https://www.haomeiwen.com/subject/ismlqhtx.html