zoukankan      html  css  js  c++  java
  • 在SQL中使用PL/SQL函数存在的问题

    -----------------------------Cryking原创------------------------------
    -----------------------转载请注明出处,谢谢!------------------------ 

    很多不了解oracle数据库的开发人员很喜欢用PL/SQL的函数、存储等来达到代码上的简洁.

    如:

    SELECT EMPNO,ENAME,DNAME,LOC FROM EMP,DEPT WHERE EMP.DEPTNO=DEPT.DEPTNO;

    这样一个SQL,开发人员可能觉得冗长(这里假设SQL冗长微笑),他们喜欢用函数,这样:

    CREATE FUNCTION F_GETDEPTINFO(PDEPTNO NUMBER,PTYPE VARCHAR2)

    RETURN VARCHAR2 AS

    V_DEPTINFO VARCHAR2(50);

    BEGIN

    IF PTYPE='DNAME' THEN

    SELECT DNAME  INTO V_DEPTINFO FROM DEPT WHERE DEPTNO=PDEPTNO;

    END IF;

    IF PTYPE='LOC' THEN

    SELECT LOC INTO V_DEPTINFO FROM DEPT WHERE DEPTNO=PDEPTNO;

    END IF;

    RETURN V_DEPTINFO;

    END;

    有的还会为函数加上异常处理,返回某些自定义的值.(我这里就没有加了)

    最后,SQL就改写为:SELECT EMPNO,ENAME,F_GETDEPTINFO(DEPTNO,'DNAME'),F_GETDEPTINFO(DEPTNO,'LOC') FROM EMP;

    这样开发人员认为SQL简洁多了,并且可能有的还认为性能会有提升.尤其是在处理某些复杂的业务逻辑的时候,SQL中PL/SQL函数使用的更为频繁.

    由于对数据库的不熟悉,尤其不善于SQL的表连接等方式,大量的函数使用导致应用变的日益缓慢起来.所以在这里建议开发的TX们也多了解一下相应的数据库原理.

    在这里我们来分析以下在SQL中使用PL/SQL函数存在的几个问题:

    1.熟悉oracle数据库的人知道在oracle数据库中存在SQL引擎和PL/SQL引擎,分别用来处理sql语句和PLSQL语句.虽然在9i之后,SQL语句和PL/SQL语句可以共享同一个

    解析器,但解析后的语句还是会在两个引擎之间进行切换,这势必会带来性能开销.

    示例:

    直接执行SQL语句

    11:17:33 SCOTT@orcl> set autot trace
    11:18:05 SCOTT@orcl> SELECT ROWNUM FROM dual CONNECT BY ROWNUM <= 1e6;
    
    1000000 rows selected.
    
    Elapsed: 00:00:10.03
    
    Statistics
    ----------------------------------------------------------
              1  recursive calls
              0  db block gets
              0  consistent gets
              0  physical reads
              0  redo size
       11846828  bytes sent via SQL*Net to client
         733734  bytes received via SQL*Net from client
          66668  SQL*Net roundtrips to/from client
              1  sorts (memory)
              0  sorts (disk)
        1000000  rows processed


    可以看到性能基本在网络的I/O上.耗时10.03秒.

    下面我们来在SQL中使用PL/SQL函数来完成同样的功能.

    11:25:11 SCOTT@orcl> CREATE FUNCTION plsql_function(p_number IN NUMBER)
    11:25:14   2  RETURN NUMBER AS
    11:25:14   3  BEGIN
    11:25:14   4    RETURN p_number;
    11:25:14   5  END plsql_function;
    11:25:15   6  /
    
    Function created.
    
    Elapsed: 00:00:00.04
    11:25:28 SCOTT@orcl> SELECT plsql_function(ROWNUM) t FROM dual CONNECT BY ROWNUM<= 1e6;
    
    1000000 rows selected.
    
    Elapsed: 00:00:13.50
    
    Statistics
    ----------------------------------------------------------
             17  recursive calls
              0  db block gets
             24  consistent gets
              0  physical reads
              0  redo size
       11846823  bytes sent via SQL*Net to client
         733734  bytes received via SQL*Net from client
          66668  SQL*Net roundtrips to/from client
              1  sorts (memory)
              0  sorts (disk)
        1000000  rows processed


     此时不仅有网络I/O,而且出现了缓存的I/O.耗时为13.50秒.

    通过autotrace我们感觉到在SQL中使用PL/SQL函数会比直接使用SQL语句要慢。

    注:这里AUTOTRACE信息不是很明显,可以通过之前介绍的DBMS_HPROF包来查看详细的性能信息.

    这里我们再分析上面两个语句的SQL_TRACE情况.

    直接使用SQL语句查询的SQL_TRACE如下:

    PARSING IN CURSOR #11 len=48 dep=0 uid=84 oct=3 lid=84 tim=1359750169623584 hv=2961471920 ad='3a9180b0' sqlid='67j3zkks88ydh'
    SELECT ROWNUM FROM dual CONNECT BY ROWNUM <= 1e6
    END OF STMT
    PARSE #11:c=5999,e=5932,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=1,plh=1731520519,tim=1359750169623579
    EXEC #11:c=0,e=218,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,plh=1731520519,tim=1359750169624022
    FETCH #11:c=1000,e=194,p=0,cr=0,cu=0,mis=0,r=1,dep=0,og=1,plh=1731520519,tim=1359750169624591
    FETCH #11:c=0,e=136,p=0,cr=0,cu=0,mis=0,r=15,dep=0,og=1,plh=1731520519,tim=1359750169625626
    ...
    FETCH #11:c=0,e=17,p=0,cr=0,cu=0,mis=0,r=15,dep=0,og=1,plh=1731520519,tim=1359750173026323

    *** 2013-02-02 04:23:05.560
    STAT #11 id=1 cnt=22441 pid=0 pos=1 obj=0 op='COUNT  (cr=0 pr=0 pw=0 time=51932 us)'
    STAT #11 id=2 cnt=22441 pid=1 pos=1 obj=0 op='CONNECT BY WITHOUT FILTERING (cr=0 pr=0 pw=0 time=30133 us)'
    STAT #11 id=3 cnt=1 pid=2 pos=1 obj=0 op='FAST DUAL  (cr=0 pr=0 pw=0 time=0 us cost=2 size=0 card=1)'
    CLOSE #11:c=1000,e=511,dep=0,type=0,tim=1359750185560442

    可以看到解析花费了一点时间,其后的EXE没有产生CPU TIME,也只有218的elapse time.

    我们再来看看使用PL/SQL函数的SQL语句的trace:

    PARSING IN CURSOR #10 len=66 dep=0 uid=84 oct=3 lid=84 tim=1359749602256931 hv=2764084342 ad='3a90d680' sqlid='9739cfkkc153q'
    SELECT plsql_function(ROWNUM) t FROM dual CONNECT BY ROWNUM <= 1e6
    END OF STMT
    PARSE #10:c=7999,e=9102,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=1,plh=1731520519,tim=1359749602256928
    EXEC #10:c=1000,e=151,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,plh=1731520519,tim=1359749602257357
    FETCH #10:c=0,e=186,p=0,cr=0,cu=0,mis=0,r=1,dep=0,og=1,plh=1731520519,tim=1359749602257894
    ...
    FETCH #10:c=95986,e=95756,p=0,cr=0,cu=0,mis=0,r=9,dep=0,og=1,plh=1731520519,tim=1359749617924141
    STAT #10 id=1 cnt=1000000 pid=0 pos=1 obj=0 op='COUNT  (cr=0 pr=0 pw=0 time=2208907 us)'
    STAT #10 id=2 cnt=1000000 pid=1 pos=1 obj=0 op='CONNECT BY WITHOUT FILTERING (cr=0 pr=0 pw=0 time=1259599 us)'
    STAT #10 id=3 cnt=1 pid=2 pos=1 obj=0 op='FAST DUAL  (cr=0 pr=0 pw=0 time=0 us cost=2 size=0 card=1)'

    可以看到解析时间以及执行时间、FETCH都高于上面.

    尤其是用户自定义的函数,其性能会比系统自带函数更低,譬如有些开发的TX不知道11G的LISTAGG函数可以实现单列转单行(有序连接列的各个元素),他们自己写了函数来完成这个功能,最终其性能是不如系统自带的函数的.

    2.在SQL中使用PL/SQL函数还存在一个比较严重的问题.那就是存在破坏读一致性的情况.

    如:

    先创建测试表EMP_TEST

    13:19:18 SCOTT@orcl> CREATE TABLE EMP_TEST AS
    13:19:27   2  SELECT ROWNUM R FROM DUAL CONNECT BY LEVEL<1000;
    
    Table created.
    
    Elapsed: 00:00:00.20


    我们这里使用DBMS_LOCK的SLEEP函数来模拟执行非常耗时的SQL的情景:

    13:35:18 SCOTT@orcl> CREATE OR REPLACE FUNCTION sleepr(p_sleep IN NUMBER)
    13:36:30   2  RETURN NUMBER AS
    13:36:30   3  BEGIN
    13:36:30   4    dbms_lock.sleep(1);
    13:36:30   5    RETURN 0;
    13:36:30   6  END sleepr;
    13:36:31   7  /
    
    Function created.
    
    Elapsed: 00:00:00.03

    创建测试函数:

    CREATE OR REPLACE FUNCTION fun_test(p_number IN NUMBER)
    RETURN NUMBER  AS
    v emp_test.r%type;
    BEGIN
      select r into v from emp_test where r=p_number;
      RETURN v;
    END fun_test;



    在会话1里我们执行这个SQL:

    --SESSION 1:

    13:38:02 SCOTT@orcl> SELECT fun_test(r)
    13:38:08   2        ,r
    13:38:08   3        ,sleepr(1)
    13:38:08   4    FROM emp_test
    13:38:08   5   WHERE rownum < 20;
    
    FUN_TEST(R)          R  SLEEPR(1)
    ----------- ---------- ----------
              1          1          0
              2          2          0
              3          3          0
              4          4          0
           null          5          0
           null          6          0
           null          7          0
           null          8          0
           null          9          0
           null         10          0
           null         11          0
           null         12          0
           null         13          0
           null         14          0
             15         15          0
             16         16          0
             17         17          0
             18         18          0
             19         19          0
    19 rows selected.
    
    Elapsed: 00:00:23.87

     注:我的SQLPLUS里这里设置了 SET NULL 'null'

    在会话2里我们执行的SQL如下:

    --SESSION 2:

    13:37:02 SCOTT@orcl> UPDATE emp_test SET r = 0 WHERE r < 15;
    
    14 rows updated.
    
    Elapsed: 00:00:00.01
    13:38:14 SCOTT@orcl> commit;
    
    Commit complete.
    
    Elapsed: 00:00:00.00


    注意执行两个SQL的时间,在会话1执行SQL后,然后执行会话2的SQL并立即提交(留意命令行前的时间)

    根据读一致性原理,会话1查询的时候,会话2还未进行更新,所以理应会话1查出的结果应当是更新前的结果,但是这里会话2的更新直接影响到了会话1了,也就是破坏了读一致性.

    由于SESSION 2更新导致了SESSION 1执行的PLSQL函数内找不到对应的R值,故返回为空了.

    这个是比较危险的,因为它没有得到我们预想的结果.

    对于这种情况,我们一般的解决方法有:1.SET TRANSACTION READ ONLY;2.FOR UPDATE加锁;3.使用闪回查询,查询指定时间的数据;4.设置事务隔离级别为serialize

    一般来说出现这种情况,开发人员用的比较多的就是2,加锁,其实这又是另一个性能开销了。。。

    3.对于SQL条件中出现的PL/SQL函数,CBO是比较难优化的,因为此时对于SQL中的PL/SQL函数是没有统计信息的,而对CBO来说,最重要的就是统计信息.所以此时CBO会采用其默认的分析处理方法来对待PL/SQL函数.

    示例:

    13:19:18 SCOTT@orcl> CREATE TABLE EMP_TEST AS
    13:19:27   2  SELECT ROWNUM R FROM DUAL CONNECT BY LEVEL<1000;
    
    Table created.
    
    Elapsed: 00:00:00.20
    13:19:28 SCOTT@orcl> EXEC DBMS_STATS.GATHER_TABLE_STATS(user, 'EMP_TEST');
    
    PL/SQL procedure successfully completed.
    
    Elapsed: 00:00:00.19
    13:20:08 SCOTT@orcl> CREATE OR REPLACE FUNCTION fun_test(p_number IN NUMBER)
    13:20:20   2  RETURN NUMBER AS
    13:20:20   3  BEGIN
    13:20:20   4    RETURN p_number;
    13:20:20   5  END fun_test;
    13:20:21   6  /
    
    Function created.
    
    Elapsed: 00:00:00.01
    13:20:22 SCOTT@orcl> alter session set events '10053 trace name context forever,level 1';
    
    Session altered.
    
    Elapsed: 00:00:00.00
    13:20:36 SCOTT@orcl> set autotrace traceonly explain
    13:20:45 SCOTT@orcl> SELECT * FROM EMP_TEST
    13:20:54   2  WHERE FUN_TEST(R)=1;
    Elapsed: 00:00:00.06
    
    Execution Plan
    ----------------------------------------------------------
    Plan hash value: 3124080142
    
    ------------------------------------------------------------------------------
    | Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
    ------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |          |    10 |    40 |     3   (0)| 00:00:01 |
    |*  1 |  TABLE ACCESS FULL| EMP_TEST |    10 |    40 |     3   (0)| 00:00:01 |
    ------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
       1 - filter("FUN_TEST"("R")=1)
    
    13:21:18 SCOTT@orcl> alter session set events '10053 trace name context off';
    
    Session altered.
    
    Elapsed: 00:00:00.00

    查看10053产生的TRACE信息发现:

    ***************************************
    BASE STATISTICAL INFORMATION
    ***********************
    Table Stats::
      Table: EMP_TEST  Alias: EMP_TEST
        #Rows: 999  #Blks:  5  AvgRowLen:  4.00
    Access path analysis for EMP_TEST
    ***************************************
    SINGLE TABLE ACCESS PATH
      Single Table Cardinality Estimation for EMP_TEST[EMP_TEST]
      No default cost defined for function FUN_TEST
      No default selectivity defined for function FUN_TEST

      Table: EMP_TEST  Alias: EMP_TEST
        Card: Original: 999.000000  Rounded: 10  Computed: 9.99  Non Adjusted: 9.99
      Access Path: TableScan
        Cost:  3.10  Resp: 3.10  Degree: 0
          Cost_io: 3.00  Cost_cpu: 3232407
          Resp_io: 3.00  Resp_cpu: 3232407
      Best:: AccessPath: TableScan
             Cost: 3.10  Degree: 1  Resp: 3.10  Card: 9.99  Bytes: 0

    CBO没有函数FUN_TEST的统计信息,无法获得选择度,因此采用了默认的1%选择度进行处理.

    虽然在SQL中使用PL/SQL函数存在很多问题,存在性能开销,但oracle一直在努力试图拉近SQL和PL/SQL的距离。

    如可以使用NO_MERGE、QB_NAME等HINT来减少SQL中PL/SQL函数执行的次数,oracle还提供了函数结果集缓存、标量子查询缓存、DETERMINISTIC函数等技术来减少函数的执行次数.

    对于CBO,oracle也提供了基于函数的索引,提供了扩展的统计信息、扩展的优化器、自定义统计信息函数等技术来提高SQL中执行PL/SQL函数的性能.

    ---

    但是总体来说,我们应当尽量使用SQL来代替PL/SQL函数(也就是能用SQL解决的问题,绝不用PL/SQL),如确实需要使用PL/SQL函数的时候,我们可以使用视图、可以使用建立虚拟列等技术来避免在SQL中使用PL/SQL函数.



     

  • 相关阅读:
    HTTP状态码
    NSData NSDate NSString NSArray NSDictionary 相互转换
    NSDictionary to jsonString || 对象转json格式
    git 上传本地文件到github
    NSAssert用法
    深入理解GCD(一)
    ug-Assertion failure in [MyClass layoutSublayersOfLayer:]
    构建之法阅读笔记01
    学习进度
    四则运算程序
  • 原文地址:https://www.cnblogs.com/snake-hand/p/3177926.html
Copyright © 2011-2022 走看看