WEB开发网
开发学院数据库Oracle Oracle数据库11g新特性:PL/SQL性能 阅读

Oracle数据库11g新特性:PL/SQL性能

 2008-12-16 13:01:32 来源:WEB开发网   
核心提示:Oracle 数据库 11g 引入了许多极有用的新特性用以提高 PL/SQL 代码的性能,但其中最引人注目的特性是原生编译和内部单元内联,Oracle数据库11g新特性:PL/SQL性能, 原生编译本身不是新特性,但无需任何前提条件(如安装 C 编译器)即可使用此特性却是一项新技术,■可以安全使用 simple_int

Oracle 数据库 11g 引入了许多极有用的新特性用以提高 PL/SQL 代码的性能,但其中最引人注目的特性是原生编译和内部单元内联。

原生编译本身不是新特性,但无需任何前提条件(如安装 C 编译器)即可使用此特性却是一项新技术。(Oracle 将这个改进的性能称为“真正的原生编译”。)此外,新的数据类型 simple_integer 使得代码在原生编译下可以更好地执行。内部单元内联是一种优化技术,利用此技术,PL/SQL 代码在编译期间可以生成有效代码。

在本文中,您将看到这些新特性的相应用例。您还会了解这些用例在不同方案中的性能:执行原生编译时、使用简单整数时、内联程序时以及在各种组合方案中。

真正的原生编译

您可能会想起 Oracle9i 数据库第 2 版中引入的原生编译;与解释形式相比,使用原生编译时 PL/SQL 程序的执行速度要快得多。尽管有上述优点,但采用率却很低,这是因为许多系统管理员都不愿意在生产数据库服务器上安装所需的 C 编译器。另外,此类编译器还要求定义用于创建 O/S 中间文件的参数 plsql_native_library_dir.

在 Oracle 数据库 11g 中,无需在服务器上安装 C 编译器或设置参数即可执行原生编译。只需在创建或重新编译存储的代码之前设置一个会话参数即可:

alter session set plsql_code_type = native;
... compile the code here ...

原生编译所需时间比解释编译长,但由于目前在 Oracle 数据库 11g 中执行进程的速度非常快,因此影响不是很明显。您可能希望在常规开发周期中使用解释编译模式,而在开发结束时使用原生编译模式,这通常是一个很好的做法。

作为 11g 移植的一部分,我用关键任务应用程序中的实际代码(一个相当大的程序包,共 5,827 行代码)做了一个试验。我在现有的 10g 数据库和新的 11g 数据库上对该代码执行了原生编译,然后使用解释编译重复了相同的操作。执行这两编译时都将 plsql_optimize_level 设置为 2.我测量了每种方案的编译时间,如下所示(以秒为单位)。

10g

11g

解释

1.66

1.64

原生

4.66

2.81

结果显而易见。执行解释编译时,完成时间非常接近。但是执行原生编译时,在 11g 下的编译时间大大缩短,约为 10g 下的 60%.因此,尽管在 11g 下执行原生编译时延长了编译时间,但仍然比 10g 下的原生编译快的多。

要了解哪些对象已使用 NATIVE 进行了编译,可以查看 USER_PLSQL_OBJECT_SETTINGS 视图:

SQL> select name, PLSQL_code_type
 2> from user_plsql_object_settings;
NAME         PLSQL_CODE_TYPE
-------------------- ---------------
DO_CALC       NATIVE
PERFECT_TRIANGLES  NATIVE
PRIME_NUMBERS    NATIVE
PRIME_NUMBERS    NATIVE
SOME_MATH      INTERPRETED
TR_BOOKINGS_TRACK  INTERPRETED
TR_SALGRADE_COMP   INTERPRETED
UPD_INT       NATIVE

还有一个适用于所有对象的类似视图 DBA_PLSQL_OBJECT_SETTINGS.

新数据类型:简单整数

如果使用新的数据类型 simple_integer,则原生编译的优势更为明显。从技术上讲,它不是真正的数据类型,而是数据类型 pls_integer 的子类型。简单整数被定义为利用硬件运算而不是软件运算。将简单整数与真正的原生编译结合使用时,性能将显著提高。看了后面的试验,您就会明白其中的原委。

由于 simple_integer 是 pls_integer 的子类型,因此继承了如下属性:它是 32 位带符号整数,可以具有介于负 2,147,483,648 和 2,147,483,647 之间的整数值。但是,它与 pls_integer 之间存在以下不同点:simple_integer 不能为空,但允许溢出 — 当所分配的值超过最大值时,它将回绕而不返回错误。

从语法上讲,在可以使用 pls_integer 的任何位置均可使用此数据类型,但要仔细注意它们之间的差别;simple_integer 的其他属性可能导致它在某些情况下不适用。

在用 pls_integer 替代 simple_integer 之前,我们来看一些您应当了解的潜在问题:

■ 此变量不能为空,因此,如果未将变量声明写成:

num1  simple_integer:= 1;

而写成:

num1  simple_integer;

则会收到编译错误:

PLS-00218: a variable declared NOT NULL must have an initialization assignment

如果在程序内将变量设置为 NULL,如:

num1 := NULL;

则会收到编译错误:

PLS-00382: expression is of wrong type

请注意这些错误消息,它们看上去可能未表达出错误的确切本质。如果您的程序要求将某个变量设置为空,则不能将该变量定义为 simple_integer。

■ 有关 simple_integer 的另一个要点涉及在超出最大值和最小值时回绕值。请记住,pls_integer 的最大正值为 2147483647。如果尝试存储一个较大值时将会发生什么?我们来看一个代码示例:

declare
v1 pls_integer := 2147483647;
begin
v1 := v1 + 1;
dbms_output.put_line('v1='||v1);
end;
/

它将抛出一个错误:

declare
*
ERROR at line 1:
ORA-01426: numeric overflow
ORA-06512: at line 4

该错误很明显,并且非常适当;您尝试超出数据类型允许的最大值。如果使用 simple_integer 而不是  

declare
v1 simple_integer := 2147483647;
begin
v1 := v1 + 1;
dbms_output.put_line('v1='||v1);
end;
/

输出应如下所示:

v1=-2147483648

请注意该值 (-2147483648),它是 simple_integer 的最小值。当您增加到最大值 (2147483647) 时,该值只是回绕到初始值,这是 simple_integer 的一个特性。请注意此行为。

将真正的原生编译和简单整数结合使用

如您所见,不能随便在任意位置使用 simple_integer;使用之前必须仔细确定边界条件(尤其是值的潜在回绕)。此外,simple_integer 是针对原生编译设计的。在解释编译中,它们可能不会提供很大的性能改进(但也不会降低性能,您将在后面看到这一点)。在真正的原生模式下,simple_integer 的性能优势尤为突出。

实际上,大多数 PL/SQL 业务应用程序都主要是使用 SQL 开发的,因此这些应用程序不会通过原生编译获得显著的性能改进。在“前一个周期”中,我使用 PL/SQL 开发了一个数据库容量规划工具,其中包含许多数值计算和统计计算,代码超过几千行。执行原生编译时,此工具的性能得到了极大改进。简单整数当时不可用,否则会带来更大的性能优势。

内部单元内联

内部单元内联是指使用子例程代码的副本替换对该子例程的调用。通常,修改后的代码运行速度更快。在 Oracle 数据库 11g 中,PL/SQL 编译器能够确定应该复制(或内联)对哪个子例程的哪些调用并做出更改,从而提高性能。

通过示例可以对此进行很好地说明。下面的代码使用对帐户余额计算得到的利息更新名为 BALANCES 的表。代码遍历表的所有记录,计算利息,然后更新表的余额列。

create or replace procedure upd_int is
/* original version */
  l_rate_type   balances.rate_type%type;
  l_bal      balances.balance%type;
  l_accno     balances.accno%type;
  l_int_rate   number;
  procedure calc_int (
    p_bal in out balances.balance%type,
    p_rate in number
  ) is
  begin
    if (p_rate >= 0) then
      p_bal := p_bal * (1+(p_rate/12/100));
    end if;
  end;
begin
  for ctr in 1..10000 loop
    l_accno := ctr;
    select balance, rate_type
    into l_bal, l_rate_type
    from balances
    where accno = l_accno;
    select decode(l_rate_type,
      'C', 1, 'S', 3, 'M', 5, 0)
    into l_int_rate
    from dual;
    for mth in 1..12 loop
      calc_int (l_bal, l_int_rate);
      update balances
      set balance = l_bal
      where accno = l_accno;
    end loop;
  end loop;
end;
/

由于所有记录类型的实际利息计算方法相同,因此我将该逻辑放在主过程内的一个单独过程 calc_int() 中。这样可以提高代码的可读性和可维护性,但效率很低。

然而,如果我编写 calc_int() 中所示的代码而不调用 calc_int(),则会创建一个速度较快的程序,如下所示:

create or replace procedure upd_int is
/* revised version */
  l_rate_type   balances.rate_type%type;
  l_bal      balances.balance%type;
  l_accno     balances.accno%type;
  l_int_rate   number;
begin
  for ctr in 1..10000 loop
    l_accno := ctr;
    select balance, rate_type
    into l_bal, l_rate_type
    from balances
    where accno = l_accno;
    select decode(l_rate_type,
      'C', 1, 'S', 3, 'M', 5, 0)
    into l_int_rate
    from dual;
    for mth in 1..12 loop
      -- this is the int calc routine
      if (l_int_rate >= 0) then
        l_bal := l_bal * (1+(l_int_rate/12/100));
      end if;
      update balances
      set balance = l_bal
      where accno = l_accno;
    end loop;
  end loop;
end;
/

修改后的代码与原始代码唯一的不同之处在于,用于计算利息的代码现在处于循环中,而不在过程 calc_int() 内。

请注意,新版本可能更快,但这不是很好的编码方法。用于计算利息的代码部分在月份循环的每次迭代时执行一次,然后针对每个帐号执行一次。由于重复执行该部分代码,因此将这部分代码单独放在一个过程 (calc_int) 中更合理,如上一版本的 upd_int 代码中所示。该方法可以使代码模块化,易于维护,且可读性极强,但效率较低。

那么,如何同时实现代码模块化和提高执行速度这两个互相冲突的目标呢?以模块化方式编写代码(像 upd_int 的第一个版本那样),然后让 PL/SQL 编译器对代码进行“优化”,使其看上去像代码的第二个版本,这种方法如何呢?

在 Oracle 数据库 11g 中可以执行此操作。只需使用较高的 PL/SQL 优化级别重新编译过程即可。完成此操作有两种方法:

begin
/*Clear the Log Group*/
execute immediate 'ALTER DATABASE CLEAR LOGFILE GROUP 3';
end;

■ 设置会话级别参数并重新编译过程:

SQL> alter session set plsql_optimize_level = 3;
Session altered.

上述设置指示 PL/SQL 编译器重写代码以内联该代码。

■ 使用 plsql 设置直接编译过程。

SQL> alter procedure upd_int
 2 compile
 3 plsql_optimize_level = 3
 4 reuse settings;
Procedure altered.

同一会话中编译的任何其他过程都不会受影响。当您要在同一会话中编译很多过程时,这是处理内联的一种较好的方法。

还可以使用编译器指令 pragma.

create or replace procedure upd_int is
  l_rate_type   varchar2(1);
...
...
begin
  pragma inline (calc_int, 'YES');
  for ctr in 1..10000 loop
...
...
end;

我添加了 pragma inline (calc_int, 'YES'); 行以便让编译器内联该过程。同样,您也可以在此处使用“NO”,以通知编译器不内联此过程,即使将 plsql_optimizer_level 设置为 3 时也是如此。

此内联过程可以使代码执行速度更快。性能改进的程度自然根据编译器可能执行内联的程度而有所不同。在本文结尾,您将看到一个使用内联的示例代码,并会看到代码内联带来的性能改进。

编译时间

当然,此优化过程使得编译器的工作效率更高。但工作效率提高多少呢?

为了回答这个问题,我采用了前面提供的实际生活应用程序代码,并在不同的组合方案(内联/非内联和解释/原生)中对其进行了编译。下面是编译时间:

内联

未内联

解释

1.70

1.64

原生

3.15

2.81

  结果是显而易见的。使用内联选项执行编译时,在解释编译模式下编译时间略有增加(大约 4%)。执行原生编译时,编译时间也有所增加,但增幅较大,约为 12%.因此,您可能希望在开发周期中使用内联/解释选项编译应用程序,然后在最后阶段使用原生编译。使用内联特性似乎没有显著增加编译时间,因此开发周期时间不会受到很大影响。

现在有一个重要问题:既然您没有更改代码,如何确认代码被内联呢?可以通过设置会话变量完成该操作:

alter session set plsql_warnings = 'enable:all';

现在,当您重新创建过程之后:

SQL> @upd_int
SP2-0804: Procedure created with compilation warnings

请注意此时发出的警告消息。要查看警告消息,请使用以下命令:

SQL> show error
Errors for PROCEDURE UPD_INT:
LINE/COL ERROR
-------- -----------------------------------------------------------------
7/5   PLW-06006: uncalled procedure "CALC_INT" is removed.
28/13  PLW-06005: inlining of call of procedure 'CALC_INT' was done

请注意最后一行,它确认了过程 calc_int 确实已被内联。

如果您要了解哪些对象是以什么级别进行编译的,可以查询视图 USER_PLSQL_OBJECT_SETTINGS:

sql> select name, plsql_optimize_level
 2> from user_plsql_object_settings;
NAME         PLSQL_OPTIMIZE_LEVEL
-------------------- --------------------
DO_CALC                 2
PERFECT_TRIANGLES            2
TR_BOOKINGS_TRACK            2
TR_SALGRADE_COMP            2
UPD_INT                 3
... and so on ...

还有一个适用于所有对象的类似视图:DBA_PLSQL_OBJECT_SETTINGS.

请记住,这是内部单元内联,只有单元内部的过程才被内联。单元外部的子例程不会被内联。

运行试验

现在通过一个可重复的试验来检验这些优点。您需要创建一个程序包的基础性版本,然后根据前面学过的概念修改变量数据类型和编译指令。

首先,创建欧几里德算法的一个实现,以算出两个数字的最大公约数。下面是 Wiki 页中的函数逻辑:

function gcd(a, b)
   if a = 0 return b
   while b ≠ 0
     if a > b
       a := a - b
     else
       b := b - a
   return a

我们将查看在这些修饰符的不同组合下执行此程序包所占用的 CPU 时间,并记录所用时间。因此,接下来创建一个用于存储 CPU 时间的表:

create table times(
 native     char(1) check (native  in ('Y', 'N')) enable,
 simple    char(1) check (simple  in ('Y', 'N')) enable,
 inlining    char(1) check (inlining in ('Y', 'N')) enable,
 centiseconds number not null,
 constraint times_pk primary key (simple, inlining, native))
/

共有三个列:原生、简单和内联,分别用于指定原生编译、简单整数和内联代码。列中的“Y”表示编译代码时使用了该特性。因此,下面的记录:

NATIVE SIMPLE INLINING CENTISECONDS
------------ ---------- -------------- -----------------------
Y       N      N         100

表示对程序执行了原生编译但未使用简单整数,未发生代码内联,并且此组合占用了 100 厘秒的 CPU 时间。

如果只使用一份程序包,请使用条件编译(在 Oracle 数据库 10g 第 2 版中引入)修改该程序包。下面是创建程序包的方法:

-- suppress these expected warnings:
--  inlining of call of procedure 'gcd' was done
--  uncalled procedure "gcd" is removed.
--  unreachable code
--  keyword "native" used as a defined name
alter session set plsql_warnings = 'enable:all, disable:06002, disable:06005, disable:06006, disable:06010'
/
alter session set plsql_ccflags = 'simple:false'
/
create package gcd_test is
 procedure time_it;
end gcd_test;
/
create package body gcd_test is
 $if $$simple $then
  subtype my_integer is simple_integer;
  simple constant times.simple%type := 'y';
 $else
  subtype my_integer is pls_integer not null;
  simple constant times.simple%type := 'n';
 $end
 function gcd(p1 in my_integer, p2 in my_integer) return my_integer is
  v1 my_integer := p1; v2 my_integer := p2;
 begin
  while v2 > 0 loop
   if v1 > v2 then
    v1 := v1 - v2;
   else
    v2 := v2 - v1;
   end if;
  end loop;
  return v1;
 end gcd;
 function exercise_gcd return number is
  -- expected value depends on no_of_iterations.
  expected_checksum my_integer := 74069926; -- 2475190;
  no_of_iterations constant my_integer := 5000; -- 1000;
  checksum my_integer := 0;
  v my_integer := 0;
  t0 number; t1 number;
 begin
  for warmup in 1..2 loop
   checksum := 0;
   t0 := dbms_utility.get_cpu_time();
   for j in 1..no_of_iterations loop
    v := gcd(j, j);
    if v <> j then
     raise_application_error(-20000, 'logic error: gcd(j, j) <> j');
    end if;
    checksum := checksum + v;
    for k in (j + 1)..no_of_iterations loop
     v := gcd(j, k);
     if gcd(k, j) <> v then
      raise_application_error(-20000, 'logic error: gcd(j, k) <> gcd(k, j)');
     end if;
     checksum := checksum + v;
    end loop;
   end loop;
   if checksum <> expected_checksum then
    raise_application_error(-20000, 'checksum <> expected_checksum: '||checksum);
   end if;
   t1 := dbms_utility.get_cpu_time();
  end loop;
  return t1 - t0;
 end exercise_gcd;
 procedure time_it is
  inlining times.inlining%type;
  native times.native%type;
  centiseconds constant times.centiseconds%type := exercise_gcd();
 begin
  if lower($$plsql_code_type) = 'native' then
   native := 'y';
  else
   native := 'n';
  end if;
  if $$plsql_optimize_level = 3 then
   inlining := 'y';
  else
   inlining := 'n';
  end if;
  insert into times(native, simple, inlining, centiseconds)
   values(time_it.native, gcd_test.simple, time_it.inlining, time_it.centiseconds);
  commit;
 end time_it;
end gcd_test;
/
show errors

程序包有足够的行内注释,使代码具有自描述性,所以此处不作详细解释。概括来说,函数 GCD() 接受两个值,并返回这两个值的最大公约数。函数 exercise_gcd() 调用 GCD() 函数,并返回执行所占用的 CPU 时间,以厘秒表示。最后,公共过程 TIME_IT() 以相应的自由度调用 EXERCISE_GCD() 函数,并向 TIMES 表中插入一条记录。

现在,您需要使用不同的修饰符调用打包过程几次,并记录每种情况下的 CPU 时间。可以通过在编译程序包之前更改条件编译变量来执行此操作。

truncate table times
/
-- Interpreted ---------------------------------------------
-- Simple:false
-- No Inlining
alter package GCD_Test compile body
 PLSQL_Code_Type = interpreted
 PLSQL_CCFlags = 'Simple:false'
 PLSQL_Optimize_Level = 2 /* no inlining */
 reuse settings
/
begin GCD_Test.Time_It(); end;
/
-- inlining
alter package GCD_Test compile body
 PLSQL_Code_Type = interpreted
 PLSQL_CCFlags = 'Simple:false'
 PLSQL_Optimize_Level = 3 /* inlined */
 reuse settings
/
begin GCD_Test.Time_It(); end;
/
-- Simple:true
-- no inlining
alter package GCD_Test compile body
 PLSQL_Code_Type = interpreted
 PLSQL_CCFlags = 'Simple:true'
 PLSQL_Optimize_Level = 2
 reuse settings
/
begin GCD_Test.Time_It(); end;
/
-- inlined
alter package GCD_Test compile body
 PLSQL_Code_Type = interpreted
 PLSQL_CCFlags = 'Simple:true'
 PLSQL_Optimize_Level = 3
 reuse settings
/
begin GCD_Test.Time_It(); end;
/
-- Native -------------------------------------------------
-- Simple:false
-- no inlining
alter package GCD_Test compile body
 PLSQL_Code_Type = native
 PLSQL_CCFlags = 'Simple:false'
 PLSQL_Optimize_Level = 2
 reuse settings
/
begin GCD_Test.Time_It(); end;
/
-- inlined
alter package GCD_Test compile body
 PLSQL_Code_Type = native
 PLSQL_CCFlags = 'Simple:false'
 PLSQL_Optimize_Level = 3
 reuse settings
/
begin GCD_Test.Time_It(); end;
/
-- Simple:true
-- no linlining
alter package GCD_Test compile body
 PLSQL_Code_Type = native
 PLSQL_CCFlags = 'Simple:true'
 PLSQL_Optimize_Level = 2
 reuse settings
/
begin GCD_Test.Time_It(); end;
/
-- inlined
alter package GCD_Test compile body
 PLSQL_Code_Type = native
 PLSQL_CCFlags = 'Simple:true'
 PLSQL_Optimize_Level = 3
 reuse settings
/
begin GCD_Test.Time_It(); end;
/

要查明上述每种方案中的性能改进程度,请使用以下代码:

spool timings.txt
<<b>>declare
 Interp_Pls_Integer_Noinline   Times.Centiseconds%type;
 Interp_Pls_Integer_Inline    Times.Centiseconds%type;
 Interp_Simple_Integer_Noinline Times.Centiseconds%type;
 Interp_Simple_Integer_Inline  Times.Centiseconds%type;
 Native_Pls_Integer_Noinline   Times.Centiseconds%type;
 Native_Pls_Integer_Inline    Times.Centiseconds%type;
 Native_Simple_Integer_Noinline Times.Centiseconds%type;
 Native_Simple_Integer_Inline  Times.Centiseconds%type;
 procedure Show_Caption(Caption in varchar2) is
 begin
  DBMS_Output.Put_Line(Chr(10)||Rpad('-', 60, '-')||Chr(10)||Chr(10)||Caption||Chr(10));
 end Show_Caption;
 procedure Show_Ratio(Var1 in varchar2, Var2 in varchar2, Ratio in number) is
 begin
  DBMS_Output.Put_Line(Rpad(Var1, 15)||'and '||Rpad(Var2, 14)||To_Char(Ratio, '99.99'));
 end Show_Ratio;
begin
 select a.Centiseconds
 into  b.Interp_Pls_Integer_Noinline
 from  Times a
 where  a.Native = 'N' and a.Simple = 'N' and a.Inlining = 'N';
 select a.Centiseconds
 into  b.Interp_Pls_Integer_Inline
 from  Times a
 where  a.Native = 'N' and a.Simple = 'N' and a.Inlining = 'Y';
 select a.Centiseconds
 into  b.Interp_Simple_Integer_Noinline
 from  Times a
 where  a.Native = 'N' and a.Simple = 'Y' and a.Inlining = 'N';
 select a.Centiseconds
 into  b.Interp_Simple_Integer_Inline
 from  Times a
 where  a.Native = 'N' and a.Simple = 'Y' and a.Inlining = 'Y';
 select a.Centiseconds
 into  b.Native_Pls_Integer_Noinline
 from  Times a
 where  a.Native = 'Y' and a.Simple = 'N' and a.Inlining = 'N';
 select a.Centiseconds
 into  b.Native_Pls_Integer_Inline
 from  Times a
 where  a.Native = 'Y' and a.Simple = 'N' and a.Inlining = 'Y';
 select a.Centiseconds
 into  b.Native_Simple_Integer_Noinline
 from  Times a
 where  a.Native = 'Y' and a.Simple = 'Y' and a.Inlining = 'N';
 select a.Centiseconds
 into  b.Native_Simple_Integer_Inline
 from  Times a
 where  a.Native = 'Y' and a.Simple = 'Y' and a.Inlining = 'Y';
 Show_Caption('Benefit of simple_integer');
 Show_Ratio('Interpreted',  'no inlining',  Interp_Pls_Integer_Noinline  / Interp_Simple_Integer_Noinline);
 Show_Ratio('Interpreted',  'inlining',    Interp_Pls_Integer_Inline   / Interp_Simple_Integer_Inline);
 Show_Ratio('Native',     'no inlining',  Native_Pls_Integer_Noinline  / Native_Simple_Integer_Noinline);
 Show_Ratio('Native',     'inlining',    Native_Pls_Integer_Inline   / Native_Simple_Integer_Inline);
 Show_Caption('Benefit of inlining');
 Show_Ratio('Interpreted',  'pls_integer',  Interp_Pls_Integer_Noinline  / Interp_Pls_Integer_Inline);
 Show_Ratio('Interpreted',  'simple_integer', Interp_Simple_Integer_Noinline / Interp_Simple_Integer_Inline);
 Show_Ratio('Native',     'pls_integer',  Native_Pls_Integer_Noinline  / Native_Pls_Integer_Inline);
 Show_Ratio('Native',     'simple_integer', Native_Simple_Integer_NoInline / Native_Simple_Integer_Inline);
 Show_Caption('Benefit of native');
 Show_Ratio('pls_integer',  'no inlining',  Interp_Pls_Integer_Noinline  / Native_Pls_Integer_Noinline);
 Show_Ratio('pls_integer',  'inlining',    Interp_Pls_Integer_Inline   / Native_Pls_Integer_Inline);
 Show_Ratio('simple_integer', 'no inlining',  Interp_Simple_Integer_Noinline / Native_Simple_Integer_Noinline);
 Show_Ratio('simple_integer', 'inlining',    Interp_Simple_Integer_Inline  / Native_Simple_Integer_Inline);
end b;
/
spool off

以下为输出内容。它显示了 CPU 时间与默认值之比:非内联、解释编译且使用 pls_integer.

------------------------------------------------------------
Benefit of simple_integer
Interpreted  and no inlining   1.00
Interpreted  and inlining    1.00
Native     and no inlining   2.19
Native     and inlining    2.79
------------------------------------------------------------
Benefit of inlining
Interpreted  and pls_integer   1.07
Interpreted  and simple_integer 1.07
Native     and pls_integer   1.16
Native     and simple_integer 1.48
------------------------------------------------------------
Benefit of native
pls_integer  and no inlining   4.78
pls_integer  and inlining    5.18
simple_integer and no inlining  10.53
simple_integer and inlining    14.49

从上述输出可以看到,默认情况下执行所占用的 CPU 时间为使用内联和 simple_integer 的原生编译的 14.49 倍 — 无论用哪种标准来衡量,这都是非常显著的性能改进。

结论

现在您应该已经了解了这些新特性的功能和用途。总结:

■ 从语法上讲,在可以使用 pls_integer 的任何位置都可以使用 simple_integer 数据类型,但要注意值的潜在回绕和非空要求。这意味着不能盲目地处处使用简单整数。如果应用程序不会意外超出最大值或低于最小值,则将简单整数与原生编译结合使用是一种很好的方法。

■ 与原生编译结合使用时,simple_integer 的优势非常明显,而与解释编译结合使用时则不明显。请注意,即使与解释编译结合使用,simple_integer 也同样是有益无害的。

■ 内联与原生编译结合使用时其优势也要比与解释编译结合使用时大得多。这一点更难解释。不严格地讲,接近普通编译后端的代码越紧凑,原生分支在域中执行智能优化(在解释分支中不可行)的机会越多。

■ 当程序比较顺从时(即,不是使用 SQL 开发的,且不使用 Oracle 数字、日期等),原生编译的优势最明显。请注意,即使符合这些条件,性能改进的程度也会在很大的范围内变化。

■ 在 Oracle 数据库 11g 中选择原生编译和内联是一件自然而然的事情。不选的唯一原因可能是编译时间较长(也许在早期的开发阶段确实如此)。

■ 可以安全使用 simple_integer 的机会不是很多。但一旦出现这种机会,就应该牢牢抓住它。

Tags:Oracle 数据库 特性

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接