-----------------------------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=,e=5932,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=1,plh=1731520519,tim=1359750169623579
EXEC #11:c=,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=,e=9102,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=1,plh=1731520519,tim=1359749602256928
EXEC #10:c=,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=,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函数.

在SQL中使用PL/SQL函数存在的问题的更多相关文章

  1. ORACLE中的PL/SQL

    一. 1.过程,函数,触发器是pl/sql编写.                2. 过程函数触发器是在Oracle中.                      3.pl/sql是非常强大的数据库过 ...

  2. SQL中Round(),Floor(),Ceiling()函数的浅析

    项目中的一个功能模块上用到了标量值函数,函数中又有ceiling()函数的用法,自己找了一些资料,对SQL中这几个函数做一个简单的记录,方便自己学习.有不足之处欢迎拍砖补充 1.round()函数遵循 ...

  3. PL/SQL中批量执行SQL脚本(不可把所有的语句都复制到New SQL Windows)

    PL/SQL中批量执行SQL脚本,不可把所有的语句都复制到New SQL Window,因为这样会导致缓冲区过大而进程卡死! 最好的办法是将要执行的SQL脚本存放到指定文件中,如C:\insert.s ...

  4. 在sql中使用了 hashbytes 函数

    在做项目的时候,在sql中使用了hashbytes函数,运用md5算法去加密一密码.代码如下 DECLARE @psw nvarchar(20) SET @psw = 'admin' SELECT h ...

  5. Sql 中常用时间处理函数

    1.Sql 中常用时间处理函数  GETDATE()  返回当前的日期和时间 DATEPART()  返回日期/时间的单独部分 DATEADD()   返回日期中添加或减去指定的时间间隔 DATEDI ...

  6. PL/SQL中直接写SQL语句和用EXECUTE IMMEDIATE方法的区别

    PL/SQL中直接写SQL语句和用EXECUTE IMMEDIATE方法的区别 在PL/SQL中在执行SQL语句时可以直接写SQL或者可以把一个SQL语句拼成一个字符串,如下: select * fr ...

  7. SQL语句、PL/SQL块和SQL*Plus命令之间的区别

    SQL语句.PL/SQL块和SQL*Plus命令之间的区别   原文链接:https://blog.csdn.net/liuzhushiqiang/article/details/12320941 在 ...

  8. sql plus 和 pl/sql无法连接远程oracle数据库

    前言:安装完oracle客户端后,可能会出现sql plus 和 pl/sql无法连接远程oracle数据库的情况,可能是以下原因: 针对sql plus连接不上: 1 可能原因:之前安装过oracl ...

  9. Oracle SQL Developer,Oracle 开发工具之toad、SQL Developer、PL/SQL Developer等比较

    参考: oracle 的几个开发工具比较 因Oracle几乎是中大型商业企业数据的首选,所以比较一下常用与Oracle的工具. Oracle SQL Developer 免费,一般开发使用足矣,常用. ...

随机推荐

  1. Spring中@Autowired注解与自动装配

    1 使用配置文件的方法来完成自动装配我们编写spring 框架的代码时候.一直遵循是这样一个规则:所有在spring中注入的bean 都建议定义成私有的域变量.并且要配套写上 get 和 set方法. ...

  2. Java多线程:Semaphore

    Semaphore为并发包中提供用于控制某资源同时可以被几个线程访问的类 实例代码: // 允许2个线程同时访问 final Semaphore semaphore = new Semaphore(2 ...

  3. prototype/constructor/__proto__之prototype简单应用

    一.简单使用构造原型加prototype造简单的轮子. 1.想jQ那样获取HTML元素,先看JS代码 function Cmf() { //创建构造函数 this.arry = [] } Cmf.pr ...

  4. @import————————css代码内部链接另外css

    在css代码里这样可以链接另外的css @import url("style.css");   @import语法结构 @import + 空格+ url(CSS文件路径地址); ...

  5. JavaScript中document.cookie

    “某些 Web 站点在您的硬盘上用很小的文本文件存储了一些信息,这些文件就称为 Cookie.”—— MSIE 帮助.一般来说,Cookies 是 CGI 或类似,比 HTML 高级的文件.程序等创建 ...

  6. 常见的HTTPS攻击方法

    0x00 背景 研究常见的https攻击方法 Beast crime breach,并针对https的特性提出一些安全部署https的建议. 针对于HTTPS的攻击,多存在于中间人攻击的环境中,主要是 ...

  7. Javascript 注意点

    prototype有助于减少function的冲突. 闭包有助于避免全部变量. this, prototype有助于实例化多个对象. 函数 函数表达式

  8. iOS 16进制颜色和UIcolor的转换

    各种颜色之间的转换,会陆续更新, 实现了 16进制颜色(HEX).RGBA.HSBA.UIColor之间的  相互转换 使用示例(加号方法,类名调用) //UIColor 转 RGB.HSB RGBA ...

  9. 关于如何在BCB中使用CodeGuard

    作者:深圳虫 来自:深圳虫网本文来自http://www.szbug.com/disparticle.aspID=4 一. 为什么写这篇东西自己在使用BCB5写一些程序时需要检查很多东西,例如内存泄漏 ...

  10. 在Hibernate中分别使用JDBC和JTA事务的方法

    在Hibernate中使用JDBC事务 Hibernate对JDBC进行了轻量级的封装,它本身在设计时并不具备事务处理功能.Hibernate将底层的JDBCTransaction或JTATransa ...