Chinaunix首页 | 论坛 | 博客
  • 博客访问: 4482936
  • 博文数量: 109
  • 博客积分: 10011
  • 博客等级: 上将
  • 技术积分: 2457
  • 用 户 组: 普通用户
  • 注册时间: 2006-10-18 19:04
文章分类

全部博文(109)

文章存档

2011年(1)

2010年(10)

2009年(36)

2008年(62)

我的朋友

分类: Oracle

2008-07-23 00:44:47

oracle10g-如何提高pl/sql程序的性能(1)

        必须说明的是,以下内容是属于学习笔记的部分,虽然PDF的E文也看得懂,但是还是觉得翻译为自己的国文看起来更加舒畅一些。
        此处原文见<>的第十一章节的内容"Tuning PL/SQL Applications for Performance"--调整Pl/sql应用程序的性能。
        一,和PL/SQL编译相关的初始化参数
        自然这里的初始化参数指的就是init文件中的参数。
               PLSQL_CCFLAGS         主要用于条件编译中
               PLSQL_CODE_TYPE    可以选择的值是NATIVE还是INTERPRETED
               PLSQL_DEBUG              true/false,true的时候,代码别编译为Interpreted类型以便调试。。
               PLSQL_NATIVE_LIBRARY_DIR        全编译时候存放库的路径
               PLSQL_NATIVE_LIBRARY_SUBDIR_COUNT    这和上一个是配对使用的,也是用于全编译。
               PLSQL_OPTIMIZE_LEVEL   特别介绍以下,不过oracle建议我们一般不要修改。
               ---------------------
                    原文不晦涩,也就不翻译了。
                    PLSQL_OPTIMIZE_LEVEL specifies the optimization level that will be used to
compile PL/SQL library units. The higher the setting of this parameter, the more effort
the compiler makes to optimize PL/SQL library units.
Values:
■ 0(主要为了向下兼容)
Maintains the evaluation order and hence the pattern of side effects, exceptions,
and package initializations of Oracle9i and earlier releases. Also removes the new
semantic identity of BINARY_INTEGER and PLS_INTEGER and restores the earlier
rules for the evaluation of integer expressions. Although code will run somewhat
faster than it did in Oracle9i, use of level 0 will forfeit most of the performance
gains of PL/SQL in Oracle Database 10g.
■ 1(一般化,不过一般不做特殊优化)
Applies a wide range of optimizations to PL/SQL programs including the
elimination of unnecessary computations and exceptions, but generally does not
move source code out of its original source order.
■ 2(和1类似,但是可能会修改你的源码,在生成的库中,代码的顺序会变化)
Applies a wide range of modern optimization techniques beyond those of level 1
including changes which may move source code relatively far from its original
location.
Generally, setting this parameter to 2 pays off in better execution performance. If,
however, the compiler runs slowly on a particular source module or if optimization
does not make sense for some reason (for example, during rapid turnaround
development), then setting this parameter to 1 will result in almost as good a
compilation with less use of compile-time resources.
The value of this parameter is stored persistently with the library unit.
最后一句话:在生成的库中总是会存储这个参数的值。
               ---------------------
               PLSQL_WARNINGS        属于编译的参数,控制可以输出的参数,可以基于session和system的级别修改
               NLS_LENGTH_SEMANTICS.  这个参数比较玄妙,这是用于控制类型为char和varchar2的列使用字节还是
                                                      字来存储相关的数据,默认是用单字节(byte),也可以是character,不影响已经存
                                                      在的数据,不影响SYSTEM和sys用户的对象。
        关于这些参数的定义可以见<>,非常的全,至于如何修改它们,则可以参考<>,这两部书都是特大部头。
        二,如何优化(或者是优化的原理是什么)
        有一点很关键的是,10g之后oracle会重新排列代码(相应的plsql_optimize_level=2)(应该来说,通常重新排列之后的算法是更好的,当然也许会存在更不好的,这个以后再说)。
        需要注意的是,你可以自己按照需要调整优化级别参数。
        对于已经编译好的对象,可以通过视图ALL_PLSQL_OBJECT_SETTINGS来知道优化的级别以及相关编译信息。
       其次,就是选择优化的时机。如果在进行优化前,先考虑一些基本的也许会更好一些。

       三、关于避免PLSQL性能问题的指导方针
       3.1 避免CPU过载
             A.使SQL语句尽可能的高效
    •  是否有合适的索引,是否正确的使用了索引
    •  是否有最新的统计数据,常常使用DBMS_STATS包来重新统计。
    •  使用EXPLAIN PLAN语句或者TKPROF工具分析语句的性能
    •  如果需要,就重写sql语句。例如可以使用强制提示
    • 考虑用FORALL替代普通的循环来执行INSERT等语句;如果需要轮询查询结果,考虑用BULK  COLLECT语句 
             B. 使函数更有效率
                 如果在SQL查询中使用函数,那么考虑建立一个基于函数的索引;尽可能的把需要使用函数的行的数量
                 降低。(由于很多的内容稍后会有比较详细的描述,所以这里不再赘述了)
             C. 优化循环
             D  尽可能地使用内建的字符串函数来处理字符串,例如搜索等等。
             E  对于条件表达式,应该把消耗最小的排在前面,这样有的时候后面复杂的测试可能可以避免。
             F  尽量减少数据类型的转换
             G  用PLS_INTEGER进行整数运算
             H  用BINARY_FLOAT 和BINARY_DOUBLE进行浮点运算
       3.2 避免内存过载
             A  定义VARCHAR2变量类型的时候,不妨大方一些,这反而可以编译溢出。
             B   把相关的过程放在一个包裹中(PACKAGE),可以减少不必要的I/O .
             C   把常常用到的包裹钉在共享内存池中,Pin packages in the shared memory pool..。可以用
                   dbms_shared_pool 包裹来处理,具体参考<                   reference>>
             D  避免编译中出现的警告。有警告提示,则应该按照提示去改正。
            
       四,记录并跟踪PL/SQL程序
         4.1  使用记录API:包裹dbms_profiler.这个包括能够提供收集并保存运行时统计数据的功能。最终的数据被保存在数据库的表格中。
         4.2  使用跟踪API:包裹DBMS_TRACE.当让要讲究跟踪的策略。对于这点,我觉得使用PL/SQL DEVELOPER很方便。

       五,减少DML语句循环的消耗、在查询中使用BULK SQL语句
        PL/SQL引擎把FORALL中的DML语句成批的发送给SQL引擎。SQL引擎通过BULK collect会批量的返回数据。这两种方式都会大大的减少两个引擎之间的通讯成本和管理成本。
        SQL语句中PL/SQL变量的绑定有三个途径:
        1:内部绑定:通过INSERT 或者UPDATE语句来完成。
        2:外部绑定:通过INSERT,UPDATE,DELETE 中的RETURNING语句来完成。
        3:定义       :通过SELECT或者FETCH语句来完成。
        此外的一种就是大量绑定(BULK BINDING)
         5.1  使用FORALL 语句
               5.1.1  FORALL是如何作用到ROLLBACKS(回滚)
                         在异常之前的语句都可以提交,异常和异常之后的无法提交。
               5.1.2  使用%bulk_rowcount属性来计算FORALL影响到的行                                
               5.1.3  使用%BULK_EXCEPTIONS属性来处理FORALL异常
               我们需要一个例子,可以从例子中得到许多有益的总结:(其中的employees的格式和数据来源于hr)                                                 
DECLARE
    TYPE empid_tab IS TABLE OF employees.employee_id%TYPE;
    emp_sr empid_tab;
    -- create an exception handler for ORA-24381
    errors NUMBER;
    dml_errors EXCEPTION;
    PRAGMA EXCEPTION_INIT(dml_errors, -24381);
BEGIN
    SELECT employee_id BULK COLLECT INTO emp_sr FROM emp_temp
    WHERE hire_date < to_date('1994-12-30','yyyy-mm-dd');
    -- add '_SR' to the job_id of the most senior employees
    FORALL i IN emp_sr.FIRST..emp_sr.LAST SAVE EXCEPTIONS
    UPDATE emp_temp SET job_id = job_id || '_SR'
    WHERE emp_sr(i) = emp_temp.employee_id;
    -- If any errors occurred during the FORALL SAVE EXCEPTIONS,
    -- a single exception is raised when the statement completes.
EXCEPTION
    WHEN dml_errors THEN -- Now we figure out what failed and why.
    FOR I IN emp_sr.FIRST..emp_sr.LAST loop
    if sql%bulk_rowcount(i)<>0 then
         dbms_output.put_line('The  row '||i||' has '||sql%bulk_rowcount(i)||' Updated !');
    else
      dbms_output.put_line('The  row '||i||' has  no rows Updated !');
    end if;
    end loop;
    errors := SQL%BULK_EXCEPTIONS.COUNT;
    DBMS_OUTPUT.PUT_LINE('Number of statements that failed: ' || errors);
    FOR i IN 1..errors LOOP      
        DBMS_OUTPUT.PUT_LINE('Error #' || i || ' occurred during '||
        'iteration #' || SQL%BULK_EXCEPTIONS(i).ERROR_INDEX);
        DBMS_OUTPUT.PUT_LINE('Error message is ' ||
        SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE));
    END LOOP;
  rollback;
END;
 The  row 1 has 1 Updated !
The  row 2 has 1 Updated !
The  row 3 has 1 Updated !
The  row 4 has 1 Updated !
The  row 5 has 1 Updated !
The  row 6 has 1 Updated !
The  row 7 has  no rows Updated !
The  row 8 has 1 Updated !
The  row 9 has 1 Updated !
The  row 10 has 1 Updated !
The  row 11 has 1 Updated !
The  row 12 has 1 Updated !
The  row 13 has  no rows Updated !
Number of statements that failed: 2
Error #1 occurred during iteration #7
Error message is ORA-12899: value too large for column  (actual: , maximum: )
Error #2 occurred during iteration #13
Error message is ORA-12899: value too large for column  (actual: , maximum: )
        
                从例子可以看出,sql%bulk_rowcount表示的是每一个sql语句影响到多少行。元素个数等于循环数.
                                            sql%bulk_exceptions则存储了有错误发生的行的信息,实际的bulk_exceptions的元素
                                           个数等于错误个数。
         5.2  用BULK COLLECT 从查询结果中提取数据到集合中
                关于这个没有什么特别好说明。就是一个returning语句稍微介绍一下:
                DELETE FROM emp_temp WHERE department_id = 30  RETURNING employee_id, last_name BULK
                       COLLECT INTO enums, names;
                其次就是可以使用limit语句,以免内存过载.
                第三,FORALL和bulk collect into 可以结合起来,但是SELECT ...BULK COLLECT除外,也就是说只能
                           用returning.
               
         六,编写精壮计算程序
         用pls_integer和binary_integer做整数运算,用BINARY_FLOAT和BINARY_DOUBLE做浮点运算。
        
         七,使用execute immediate 和cursor变量来调整性能
         尽量不要用dbms_sql来执行动态sql了(个人任务这个包过时的可能性比较大),现在用execute immedaite能
  够很好的执行各种动态的sql,关于这个可以参考我的另外一篇文章:动态执行sql的几个方式     
         DBMS_SQL的速度和紧凑性上都比execute immediate来的差。
        
        八,在过程调用中使用NOCOPY编译提示指令
        IN/OUT的过程传参,一般情况是按值来进行的,这就是说在调用子过程前,需要一个中间变量,然后把值复制给这个中间变量,临了还要拷贝回来,如果参数的数据许多且结构复杂,那么可能就需要消耗许多的时间。
        使用NOCOPY指令,这样可以强制调用过程的时候使用地址传递参数。这样的好处是快,但是也有个坏处,如果子程序异常结束了,那么参数也可能发生了改变。
         例子:
         DECLARE
    TYPE EmpTabTyp IS TABLE OF employees%ROWTYPE;
    emp_tab EmpTabTyp := EmpTabTyp(NULL); -- initialize
    t1 NUMBER;
    t2 NUMBER;
    t3 NUMBER;
    PROCEDURE get_time (t OUT NUMBER) IS
    BEGIN t := DBMS_UTILITY.get_time; END;
    PROCEDURE do_nothing1 (tab IN OUT EmpTabTyp) IS
    BEGIN NULL; END;
    PROCEDURE do_nothing2 (tab IN OUT NOCOPY EmpTabTyp) IS
    BEGIN NULL; END;
BEGIN
    SELECT * INTO emp_tab(1) FROM employees WHERE employee_id = 100;
    emp_tab.EXTEND(49999, 1); -- copy element 1 into 2..50000
    get_time(t1);
    do_nothing1(emp_tab); -- pass IN OUT parameter
    get_time(t2);
    do_nothing2(emp_tab); -- pass IN OUT NOCOPY parameter
    get_time(t3);
    DBMS_OUTPUT.PUT_LINE('Call Duration (secs)');
    DBMS_OUTPUT.PUT_LINE('--------------------');
    DBMS_OUTPUT.PUT_LINE('Just IN OUT: ' || TO_CHAR((t2 - t1)/100.0));
    DBMS_OUTPUT.PUT_LINE('With NOCOPY: ' || TO_CHAR((t3 - t2))/100.0);
END;
      
          限制性(比较常见的):
          通过DBLINK调用的过程,或者是外部过程例如DLL之类的,那么这个HINT无效。
          实参需要一个隐式数据转换的
阅读(2293) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~