随着oracle推出最新版本的数据库oracle database 11g,关于这个新一代oracle数据库的诸多新特性也逐渐浮出了水面。要从琳琅满目的新特性里拣出其中的一两项来作为开篇内容实在很难。本文将重点关注关于服务器端查询结果及pl/sql函数缓存的新特性……
【it专家网独家】缓存新看点
oracle database 11g增加了关于数据缓存的功能栏。在过去,oracle 数据库缓存数据库块。这些数据库块能够被缓存在很多区域,例如默认缓冲池、保持缓冲池和回收缓冲池。不过数据库的缓冲对象总是数据块,而构造块用于构建结果集。
从oracle database 11g开始,oracle数据库也能够缓存结果集了。如果你要对变化缓慢甚至从不改变的数据不断地执行同一个查询,你就会发现这个全新的服务器端查询结果缓存功能非常管用。几乎所有的应用软件都能而且也将从这个特性中受益。
这个性能可以这样概括:当你执行一个查询时,oracle database 11g会先行查看这个查询的结果是不是已经有人计算过这个查询的结果并且将其缓存起来,如果是的,那么数据库就会从服务器端查询结果缓存中重新获取查询结果,而不会去集结所有的数据库块并整个的从头开始计算结果。执行第一次查询要花上和平常查询一样的时间(因为数据库是在计算结果),不过接下来的查询几乎可以算的上是即时的了,因为查询的结果是直接返回的,而不是计算然后返回的。
有观点认为这个特性与实时物化视图类似(just-in-time materialized view)。在oracle8i database release 1中,oracle引入了物化视图的概念。凭借这个,dba们就可以创建概要表,例如,他们能够创建一个索引,而数据库优化器会识别到这个概要表的存在,而且如果可能的话,优化器会利用它来对查询作出应答,而不是查询并处理储存在基础表中的详细信息。这种运行方式很有用,但是相当的静态,更像是一个索引结构(indexing scheme.)。而oracle database 11g的服务器端查询结果缓存功能只利用一个缓存就能同时做到有效的创建并维持物化视图。
通过以下这个例子,可以将all_objects表复制到一个表中:
sql> create table t
2 as
3 select *
4 from all_objects;
table created.
下面创建一个执行报告的小功能――一个针对上面那个表的查询。为了更清楚的昭示这个查询功能的不同之处,可以对内部的循环连接不作任何操作(正常情况下,loop会在行中出现并执行计算):
sql> create or replace procedure 2 my_function 3 as 4 begin 5 for x in 6 (select owner, 7 object_type, 8 count(*) cnt 9 from t 10 group by owner, object_type 11 order by owner, object_type ) 12 loop 13 -- do_something 14 null; 15 end loop; 16 end; 17 / procedure created.
接下来,这个操作重复执行三次,注意执行每次调用所花费的时间:
sql> set timing on sql> exec my_function pl/sql procedure successfully completed. elapsed: 00:00:01.54 sql> exec my_function pl/sql procedure successfully completed. elapsed: 00:00:00.10 sql> exec my_function pl/sql procedure successfully completed. elapsed: 00:00:00.11 sql> set timing off
注意到第一次执行用了相当长的时间,大约1.5秒,这是因为物理输入/输出操作需要从磁盘将数据读入到传统的缓冲存储器中。由于要用来应答查询的块可以在缓存中找到,而不用再到磁盘查找,所以第二次和第三次执行行所花的时间就很短了,只有大概0.1秒。再来比较以下,如果在查询中加入“缓存结果”的暗示,又会发生什么变化呢:
sql> create or replace procedure
2 my_function
3 as
4 begin
5 for x in
6 (select /*+ result_cache */
7 owner,
8 object_type,
9 count(*) cnt
10 from t
11 group by owner, object_type
12 order by owner, object_type )
13 loop
14 -- do_something
15 null;
16 end loop;
17 end;
18 /
procedure created.
sql> set timing on
sql> exec my_function
pl/sql procedure successfully completed.
elapsed: 00:00:00.10
sql> exec my_function
pl/sql procedure successfully completed.
elapsed: 00:00:00.00
sql> exec my_function
pl/sql procedure successfully completed.
elapsed: 00:00:00.01
sql> set timing off
注意由于第一次执行的结果是组合而来的,所以第一次运行只用了约0.1秒,不过,接下来的执行速度就快的难以致信了,有时候快到就像是即时出现的一样。
更美妙的是数据库可以使缓存失效而重新刷新,并且这个过程对应用软件是完全开放的。应用软件不用再担心受到“陈旧”或者无效结果的干扰了。举个例子,如果更新一单行,就会改变运行结果:
sql> update t
2 set owner = lower(owner)
3 where rownum = 1;
1 row updated.
sql> commit;
commit complete.
―i observe the following behavior:
sql> set timing on
sql> exec my_function
pl/sql procedure successfully completed.
elapsed: 00:00:00.10
sql> exec my_function
pl/sql procedure successfully completed.
elapsed: 00:00:00.00
sql> exec my_function
pl/sql procedure successfully completed.
elapsed: 00:00:00.01
sql> set timing off
注意update后的第一次执行时间又回到了约0.1秒的水平,因为它需要构建新的结果。而受益于第一次运行所以接下来的执行结果还是即时出现的。
如果你能停下来仔细考虑一下自己的应用软件,你就可能会发现服务器端结果缓存能派上用场的地方肯定不止一个。它能提供一些物化视图所具有的大部分好处,但却不需要安装也不需要投入额外的管理费用。
这就完了?不,还有更精彩……
正如oracle发言人所说的:“如果你以为服务器端结果缓存已经相当不错了,别着急,先看看这个再说。”他们所说的正是oracle database 11g在缓存方面的另外一个新特性,那就是pl/sql函数结果缓存。尽管上述的服务器端结果缓存也与sql结果集缓存有关,这个服务器端结果缓存特性的延伸特性能够缓存pl/sql函数和程序调用结果。
过去,如果你上千次调用一个pl/sql函数,而每次调用都得花费一秒钟时间,那么一千次调用就得用上一千秒。而这个新的函数结果缓存特性,依赖于对函数的输入数据以及数据是否以函数变化为基础,一千次函数调用总共要花费的时间不过一秒钟。还是通过例子来说明吧:首先创建两个函数,除了名称和编译参数不同外,其他都相同。它们都会访问预先创建的表t:
sql> create or replace
2 function not_cached
3 ( p_owner in varchar2 )
4 return number
5 as
6 l_cnt number;
7 begin
8 select count(*)
9 into l_cnt
10 from t
11 where owner = p_owner;
12 dbms_lock.sleep(1);
13 return l_cnt;
14 end;
15 /
function created.
sql> create or replace
2 function cached
3 ( p_owner in varchar2 )
4 return number
5 result_cache
6 relies_on(t)
7 as
8 l_cnt number;
9 begin
10 select count(*)
11 into l_cnt
12 from t
13 where owner = p_owner;
14 dbms_lock.sleep(1);
15 return l_cnt;
16 end;
17 /
function created.
除了函数名以外,这两个函数唯一的不同就是编译参数分别是result_cache和relies_on。result_cache指令告诉oracle数据库你想要将这个函数的结果保存起来,这样如果其他人用相同的输入数据再次调用这个函数,那么代码实际上不会真正地执行,而调用者会接受到的结果就是已经保存的已知结果。relies_on从句则会告诉数据库什么时候使这个函数结果缓存值失效,即表t被修改(这个操作会改变已缓存函数的结果,因此必须重新执行)的时候。注意,为了功能生效,在两个函数中设置了延时一秒,这个延时调用在函数真正调用和结果重复利用时表现得非常明显。
开始时先调用传统(非缓存)函数三次,启动计时功能:
sql> exec dbms_output.put_line( not_cached( 'scott' ) ); 6 pl/sql procedure successfully completed. elapsed: 00:00:01.93 sql> exec dbms_output.put_line( not_cached( 'scott' ) ); 6 pl/sql procedure successfully completed. elapsed: 00:00:01.29 sql> exec dbms_output.put_line( not_cached( 'scott' ) ); 6 pl/sql procedure successfully completed. elapsed: 00:00:01.07
正如你所见到的,每次函数调用都至少耗时一秒钟――由函数加上它所执行的sql完成的处理要耗费稍长于一秒钟的时间。接下来,这个函数的缓存版本试试看:
sql> exec dbms_output.put_line( cached( 'scott' ) ); 6 pl/sql procedure successfully completed. elapsed: 00:00:01.09 sql> exec dbms_output.put_line( cached( 'scott' ) ); 6 pl/sql procedure successfully completed. elapsed: 00:00:00.01 sql> exec dbms_output.put_line( cached( 'scott' ) ); 6 pl/sql procedure successfully completed. elapsed: 00:00:00.01
再如你所见,第一次执行耗时一秒多钟,就像非缓存版本一样;但是接下来的执行速度赶得上光速了,原因仅仅是由于没有真正执行已缓存的函数。如果修改此函数倚赖的表或者改变输入参数,我们看到数据库进行了准确无误的运行:
sql> update t set owner = initcap(owner) where rownum = 1; 1 row updated. sql> commit; commit complete. sql> exec dbms_output.put_line( cached( 'scott' ) ); 6 pl/sql procedure successfully completed. elapsed: 00:00:01.25
由于需要更新结果缓存,已缓存函数的第一次执行耗时一秒多钟,不过接下来的执行速度得益于这个缓存的结果而大大缩短:
sql> exec dbms_output.put_line( cached( 'scott' ) ); 6 pl/sql procedure successfully completed. elapsed: 00:00:00.01 sql> exec dbms_output.put_line( cached( 'scott' ) ); 6 pl/sql procedure successfully completed. elapsed: 00:00:00.01
为了显示pl/sql函数结果缓存功能清楚不同的输入数据会导致不同的结果,这里用另外一个名称来调用这个已缓存的函数:
sql> exec dbms_output.put_line( cached( 'sys' ) ); 29339 pl/sql procedure successfully completed. elapsed: 00:00:01.21 sql> exec dbms_output.put_line( cached( 'sys' ) ); 29339 pl/sql procedure successfully completed. elapsed: 00:00:00.01
注意第一次执行耗时相对较长(用于建立结果缓存),接下来的一次执行就快多了,而且用别的名称来调用这个函数并不会使其他的已缓存结果失效或丢失:
sql> exec dbms_output.put_line( cached( 'scott' ) ); 6 pl/sql procedure successfully completed. elapsed: 00:00:00.00
使用scott所谓输入数据来进行函数调用会一直保持高速执行,直到缓存被迫失效或者空间用完了且必须给其他的元素让路为止。
同样的,这个性能的实现也不需要对你的应用软件做大规模的重新构建,事实上,不需要一丁点的改动。编译参数result_cache能够激活这个功能,而且无论哪个客户调用这个函数都能受益。例如,对oracle application express的非正式测试显示它能让你的运行时间比正常基准降低约15%――当然这个数值也是因人而异的.
异常处理
oracle对错误的处理使用的是异常处理,即when others then程序构建语言。虽然oracle能使它运行的更加安全,但是这种语言的使用还是有其致命缺陷。其中一个问题就是有太多的人再使用when others从句时并没有在后面跟着调用raise或raise_application_error。这种做法会把错误隐藏起来。而事实上,错误发生了,但却没有通过任何有效的方法处理;而只是被忽略掉。而调用了错误代码的用户并没有意识到出现了严重的错误,没有意识到这些代码已经没用,他们反而会理所当然地认为一切都在朝着成功的方向迈进。
由于不恰当地运用when others从句(没有紧跟着用raise语句抛出异常)会导致pl/sql程序单元频繁地出现异常行为。错误被隐藏,错误处理失去效用,输出的结果不是逻辑性误用数据就是错误的结果。
现在,在oracle database 11g中,这些被破坏的代码也很容易发现。当你怀疑异常出现了,并且通过以上方式隐藏起来了,你能够很快的验证你的预感。可以考虑使用以下无害的程序:
sql> create table t( x varchar2(4000) ); table created. sql> create or replace 2 procedure maintain_t 3 ( p_str in varchar2 ) 4 as 5 begin 6 insert into t 7 ( x ) values 8 ( p_str ); 9 exception 10 when others 11 then 12 -- call some log_error() routine 13 null; 14 end; 15 / procedure created.
真是易如反掌。这样就不会出现任何差错了,以防万一,可以使用以下的实用例行程序来记录错误。这个例行程序会在某处写上一个错误信息,不过无论谁调用了这个例行程序都不会知道有一个无法修正的异常出现了:
sql> exec maintain_t( rpad( 'x', 4001, 'x' ) ); pl/sql procedure successfully completed.
it sure looks successful, but it wasn't:
sql> select * from t; no rows selected
当有人用了这个例行程序时,疑惑也会随之而来:oracle数据库崩溃了;处理运行成功,而数据确实错误的。事实上,问题在于被隐藏的错误。要在oracle database 11g里找出这些错误,可以先对代码进行以下的操作:
sql> alter procedure maintain_t compile 2 plsql_warnings = 'enable:all' 3 reuse settings 4 / sp2-0805: procedure altered with compilation warnings sql> show errors procedure maintain_t errors for procedure maintain_t: line/col error ---------- --------------------------------------- 9/8 plw-06009: procedure "maintain_t" others handler does not end in raise or raise_application_error
一串应用软件的名单(包括源代码行)会立即呈现在你眼前,马上加入一个简单raise语句,以查看隐藏的错误来自哪里。这样就万事大吉了。
非凡源自点滴
名人言:“点滴筑就非凡人生。”所以本文也以一个小特性来收尾吧,这个小特性能让你的编码人生稍显轻松,让你的pl/sql语言更加完善:
sql> create sequence this_is_nice; sequence created. sql> declare 2 n number; 3 begin 4 n := this_is_nice.nextval; 5 dbms_output.put_line( n ); 6 end; 7 / 1 pl/sql procedure successfully completed.
比较一下在oracle database 10g及更早的版本中运行完之后会发生什么。在oracle database 11里,不再有选择语句from dual了。pl/sql也因此更加完善。
it专家网原创文章,未经许可,严禁转载!