在SQL中使用PL/SQL函数存在的问题
很多不了解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函数存在的问题的更多相关文章
- ORACLE中的PL/SQL
一. 1.过程,函数,触发器是pl/sql编写. 2. 过程函数触发器是在Oracle中. 3.pl/sql是非常强大的数据库过 ...
- SQL中Round(),Floor(),Ceiling()函数的浅析
项目中的一个功能模块上用到了标量值函数,函数中又有ceiling()函数的用法,自己找了一些资料,对SQL中这几个函数做一个简单的记录,方便自己学习.有不足之处欢迎拍砖补充 1.round()函数遵循 ...
- PL/SQL中批量执行SQL脚本(不可把所有的语句都复制到New SQL Windows)
PL/SQL中批量执行SQL脚本,不可把所有的语句都复制到New SQL Window,因为这样会导致缓冲区过大而进程卡死! 最好的办法是将要执行的SQL脚本存放到指定文件中,如C:\insert.s ...
- 在sql中使用了 hashbytes 函数
在做项目的时候,在sql中使用了hashbytes函数,运用md5算法去加密一密码.代码如下 DECLARE @psw nvarchar(20) SET @psw = 'admin' SELECT h ...
- Sql 中常用时间处理函数
1.Sql 中常用时间处理函数 GETDATE() 返回当前的日期和时间 DATEPART() 返回日期/时间的单独部分 DATEADD() 返回日期中添加或减去指定的时间间隔 DATEDI ...
- PL/SQL中直接写SQL语句和用EXECUTE IMMEDIATE方法的区别
PL/SQL中直接写SQL语句和用EXECUTE IMMEDIATE方法的区别 在PL/SQL中在执行SQL语句时可以直接写SQL或者可以把一个SQL语句拼成一个字符串,如下: select * fr ...
- SQL语句、PL/SQL块和SQL*Plus命令之间的区别
SQL语句.PL/SQL块和SQL*Plus命令之间的区别 原文链接:https://blog.csdn.net/liuzhushiqiang/article/details/12320941 在 ...
- sql plus 和 pl/sql无法连接远程oracle数据库
前言:安装完oracle客户端后,可能会出现sql plus 和 pl/sql无法连接远程oracle数据库的情况,可能是以下原因: 针对sql plus连接不上: 1 可能原因:之前安装过oracl ...
- Oracle SQL Developer,Oracle 开发工具之toad、SQL Developer、PL/SQL Developer等比较
参考: oracle 的几个开发工具比较 因Oracle几乎是中大型商业企业数据的首选,所以比较一下常用与Oracle的工具. Oracle SQL Developer 免费,一般开发使用足矣,常用. ...
随机推荐
- [LeetCode OJ] Best Time to Buy and Sell Stock I
Say you have an array for which the ith element is the price of a given stock on day i. If you were ...
- 第三篇、调优之路 Apache调优
1. 简介 在第一篇中整合了apache + tomcat ,利用了apache解析静态文件为tomcat解压.但是在测试机上发现两者性能不足,不能充分利用服务器的性能,该篇中将对apache进行性 ...
- (转载)无缝滚动图片的js和jquery两种写法
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- JQUERY1.9学习笔记 之可见性过滤器(一) 隐藏选择器
描述:选择所有隐藏的元素. jQuery( ":hidden" ) 例:显示出所有隐藏的div元素,并对隐藏的input元素计数. <!doctype html>< ...
- dede导航设置成单页面内容
有时顶级导航可能就是一个单页面 如公司简介 联系我们等 方法一:直接在导航栏填写内容 常规设置 二高级选项设置模板 三 填写页面内容 四 模板页面调用 内容 可在栏目模板中用{dede:field.c ...
- python操作memcache
48.python 操作memcached Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存 ...
- Java中间件
传统的HTML已经满足不了如今web系统的诸多的功能需求,建立一个交互式的Web,便诞生了各种Web开发语言,如ASP,JSP,PHP等,这些语言与传统的语言有着密切的联系,如JSP基于Java语言. ...
- Java中传参的值传递和引用传递问题(转)
今天遇到了一个java程序,需要用参数来返回值(虽然最后用另一种方法实现了),在网上看到这样一篇文章,很受启发. 本文章来自于http://hi.baidu.com/xzhilie/blog/item ...
- BZOJ 1200 木梳
Description Input 第一行为整数L,其中4≤L≤100000,且有50%的数据满足L≤104,表示木板下侧直线段的长.第二行为L个正整数A1,A2,…,AL,其中Ai≤108 Outp ...
- [Codeforces Round#286] A.Mr. Kitayuta, the Treasure Hunter 【Normal DP..】
题目链接:CF#286 - A 这场CF就这样爆零了...我真是太蒟蒻了... 题目分析 比赛的时候看到A题就发现不会,之后一直也没想出来,于是就弃了,还好不提交也不掉Rating... 比赛后看评论 ...