oracle语句call,[翻译]Oracle 12c数据库中的UTL_CALL_STACK包

本帖最后由 newkid 于 2014-12-3 00:27 编辑

前几天有关于这个包的题目:http://www.itpub.net/thread-1894551-1-1.html

我想起以前看过steven的一篇相关文章,就翻译出来贴在这里。

复杂的调用堆栈分析

原文链接:http://www.oracle.com/technetwork/issue-archive/2014/14-jan/o14plsql-2045346.html

作者:Steven Feuerstein (Oracle ACE Director)

Oracle 12c数据库中的UTL_CALL_STACK包给了开发者更好的答案。

这是关于 Oracle 12c数据库 Release 1中PL/SQL新功能的第三篇也是最后一篇文章,它将专注于新的UTL_CALL_STACK包。

调用堆栈,出错堆栈,和错误的回溯

在 Oracle 12c数据库之前, Oracle 数据库提供了几种DBMS_UTILITY函数,以回答程序员在开发、排错、维护他们的程序时所问的几个关键问题,这些函数极其有用。然而,有待改善的空间依然存在,这就是为什么Oracle 12c数据库加入了UTL_CALL_STACK包。

在我深入UTL_CALL_STACK之前,让我们复习一下三个DBMS_UTILITY函数,它们被UTL_CALL_STACK包重新构想了。

DBMS_UTILITY.FORMAT_CALL_STACK。这是在Oracle7中引入的,DBMS_UTILITY.FORMAT_CALL_STACK这个内置函数返回一个格式化的字符串,它显示了执行调用堆栈:直至此函数的调用点处的所有过程或者函数的调用顺序。换句话说,这个函数回答了这个问题:“我是怎么来到这里的?”

代码清单1展示了DBMS_UTILITY.FORMAT_CALL_STACK函数以及格式化子串的例子。

代码清单 1: DBMS_UTILITY.FORMAT_CALL_STACK函数的展示

SQL> CREATE OR REPLACE PROCEDURE proc1

2  IS

3  BEGIN

4     DBMS_OUTPUT.put_line (DBMS_UTILITY.format_call_stack);

5  END;

6  /

SQL> CREATE OR REPLACE PACKAGE pkg1

2  IS

3     PROCEDURE proc2;

4  END pkg1;

5  /

SQL> CREATE OR REPLACE PACKAGE BODY pkg1

2  IS

3     PROCEDURE proc2

4     IS

5     BEGIN

6        proc1;

7     END;

8  END pkg1;

9  /

SQL> CREATE OR REPLACE PROCEDURE proc3

2  IS

3  BEGIN

4     FOR indx IN 1 .. 1000

5     LOOP

6        NULL;

7     END LOOP;

8

9     pkg1.proc2;

10  END;

11  /

SQL> BEGIN

2     proc3;

3  END;

4  /

——————— PL/SQL Call Stack ———————

object handle    line number   object name

000007FF7EA83240              4   procedure HR.PROC1

000007FF7E9CC3B0              6   package body HR.PKG1

000007FF7EA0A3B0              9   procedure HR.PROC3

000007FF7EA07C00              2   anonymous block

对于跟踪和错误日志而言这是非常有用的信息,但是使用DBMS_UTILITY.FORMAT_CALL_STACK及其返回的字符串也有一些缺点:

如果你调用一个包中的子程序,格式化的调用堆栈只会显示包的名字,而不显示子程序的名字,当然也不会显示在那个子程序中嵌套定义的子程序名。

如果你只需要最近执行的子程序名字,你不得不解析这个字符串。这并不难,但你不得不书写和维护更多的代码。

这个“object handle”的值,对于所有实际的目的而言全是“噪音”。 PL/SQL程序员(至少,在ORACLE之外的程序员)从来不会使用这个值。

DBMS_UTILITY.FORMAT_ERROR_STACK。这是在Oracle7中引入的,DBMS_UTILITY.FORMAT_ERROR_STACK 这个内置函数和SQLERRM一样,返回的是和当前错误(SQLCODE返回的值)所关联的错误信息。

DBMS_UTILITY.FORMAT_ERROR_STACK 函数和 SQLERRM 在两个方面有所不同:

它可以返回长达1,899字符的错误信息,从而在错误堆栈增长时避免了信息截断的问题(或者至少将可能性降到极低)。SQLERRM会截断信息只留下510个字符。

你不能将一个错误代码传给这个函数,它也不能用来返回一个错误代码的所代表的错误信息。

按照规则,你应该在你的异常处理器中调用这个函数,然后将错误堆栈保存在你的错误日志表中用以事后分析。

DBMS_UTILITY.FORMAT_ERROR_BACKTRACE。这是在Oracle 10g数据库引入的,DBMS_UTILITY.FORMAT_ERROR_BACKTRACE内置函数返回一个格式化的字符串堆栈,堆栈中的程序及其行号可以回溯到错误被最先抛出的那一行。

这个函数把L/SQL中的一条大沟填平了。在Oracle9i数据库以及更早的版本,一旦你在PL/SQL块中处理了异常,你就无法确定错误是在哪一行发生的(这个对于开发者来说可能是最重要的信息)。

如果你确实想看到这个信息,你不得不允许异常不被处理,这时你可以看到完整的错误回溯信息被显示在屏幕上,或者以其他方式展示给用户。

DBMS_UTILITY.FORMAT_ERROR_BACKTRACE产生了及其有用的信息。我建议,无论何时,当你处理一个错误的时候,你都调用DBMS_UTILITY.FORMAT_ERROR_BACKTRACE函数并且把跟踪信息写入你的错误日志表。它会在解决错误发生的原因时发挥很大的帮助作用。

然而,就如DBMS_UTILITY.FORMAT_CALL_STACK函数一样,关键的信息(子程序的名称以及出错的行数)被藏在格式化的字符串之内。并且,更糟糕的是,你看不到包内的子程序的名字。

所有这些缺陷,在Oracle 12c数据库中的新包UTL_CALL_STACK中都得到了解决。

新的UTL_CALL_STACK包

UTL_CALL_STACK包提供了现在执行的子程序的相关信息。虽然包的名称看起来好像只提供了执行堆栈,其实它也提供了对出错堆栈和错误回溯信息的访问。

每个堆栈包含了深度(位置),你可以要求这三种堆栈中的每一种的某一个特定深度的信息,这在整个包都是可见的。这意味着你不再需要解析格式化字符串来找到你所需要的特定信息。

UTL_CALL_STACK 针对 DBMS_UTILITY.FORMAT_CALL_STACK的最重要的改善之一,是你可以获得带有单元限定的名字,它拼接了单元的名字,所有父程序的名字,以及子程序名。然而,在错误回溯堆栈中没有这些额外信息。表1包含了UTL_CALL_STACK包中的子程序的清单及其描述。

子程序名         描述

BACKTRACE_DEPTH  返回回溯堆栈中的元素数量

BACKTRACE_LINE   返回指定深度的那个程序单元的行号

BACKTRACE_UNIT   返回指定深度的那个程序单元的名称

CONCATENATE_SUBPROGRAM   返回拼接形式的程序单元限定的名字

DYNAMIC_DEPTH    返回调用堆栈中的子程序的数量,包括一路上调用的 SQL, Java, 和其他的非PL/SQL的上下文调用——例如,假设A调用B调用C调用B, 这个堆栈如果写成一行,看起来会是这样子(下面是动态深度):

A B C B

4 3 2 1

ERROR_DEPTH      返回调用堆栈中的错误数量

ERROR_MSG        返回指定深度的错误信息

ERROR_NUMBER     返回指定深度的错误代号

LEXICAL_DEPTH    返回指定动态深度的子程序的词汇嵌套级别

UNIT_LINE        返回指定深度的那个程序单元的行号

SUBPROGRAM       返回指定深度的程序单元限定的名字

表1: UTL_CALL_STACK包中的子程序

首先,让我们来看看如何用UTL_CALL_STACK来模拟DBMS_UTILITY.FORMAT_CALL_STACK函数并且显示完整的调用堆栈。为了做到这一点,你必须以深度来遍历堆栈中的条目。代码清单2中的format_call_stack_12c过程精确地完成了这个任务。

代码清单2: format_call_stack_12c过程调用了UTL_CALL_STACK子程序

SQL> CREATE OR REPLACE PROCEDURE format_call_stack_12c

2  IS

3  BEGIN

4     DBMS_OUTPUT.put_line (

5        'LexDepth Depth LineNo Name');

6     DBMS_OUTPUT.put_line (

7        '-------- ----- ------ ----');

8

9     FOR the_depth IN REVERSE 1 ..

10                          utl_call_stack.dynamic_depth ()

11     LOOP

12        DBMS_OUTPUT.put_line (

13              RPAD (

14                 utl_call_stack.lexical_depth (

15                    the_depth),

16                 9)

17           || RPAD (the_depth, 5)

18           || RPAD (

19                 TO_CHAR (

20                    utl_call_stack.unit_line (

21                       the_depth),

22                    '99'),

23                 8)

24           || utl_call_stack.concatenate_subprogram (

25                 utl_call_stack.subprogram (

26                    the_depth)));

27     END LOOP;

28  END;

29  /

这是代码清单2中对UTL_CALL_STACK包的几处关键调用:

第9和第10行设置了FOR循环,利用DYNAMIC_DEPTH函数,从堆栈中的最后一个元素开始,以反序遍历到堆栈中的第一个元素。

第14行调用LEXICAL_DEPTH函数来显示堆栈中每个元素的深度。

第20行和21调用UNIT_LINE来获得程序单元的行号。

第24和第25行先调用SUBPROGRAM来获得堆栈中当前深度的元素。然后用CONCATENATE_SUBPROGRAM获得子程序的完整的带限定的名字。

然后我在pkg.do_stuff过程使用了代码清单2中的format_call_stack_12c,并且执行了这个过程,如代码清单3所示。

代码清单 3:  pkg.do_stuff 过程调用了 format_call_stack_12c 过程

SQL> CREATE OR REPLACE PACKAGE pkg

2  IS

3     PROCEDURE do_stuff;

4  END;

5  /

SQL> CREATE OR REPLACE PACKAGE BODY pkg

2  IS

3     PROCEDURE do_stuff

4     IS

5        PROCEDURE np1

6        IS

7           PROCEDURE np2

8           IS

9              PROCEDURE np3

10              IS

11              BEGIN

12                 format_call_stack_12c;

13              END;

14           BEGIN

15              np3;

16           END;

17        BEGIN

18           np2;

19        END;

20     BEGIN

21        np1;

22     END;

23  END;

24  /

SQL> BEGIN

2     pkg.do_stuff;

3  END;

4  /

LexDepth  Depth   LineNo     Name

———————   ——————— ————————   ——————————————————————————

0         6       2          __anonymous_block

1         5      21          PKG.DO_STUFF

2         4      18          PKG.DO_STUFF.NP1

3         3      15          PKG.DO_STUFF.NP1.NP2

4         2      12          PKG.DO_STUFF.NP1.NP2.NP3

0         1      12          FORMAT_CALL_STACK_12C

下一步我将用UTL_CALL_STACK包来显示抛出当前异常的程序单元名字和所在行号。在代码清单4中,我创建并且执行了一个名为BACKTRACE_TO的函数,它“隐藏”了对UTL_CALL_STACK子程序的调用。在每次对BACKTRACE_UNIT和BACKTRACE_LINE的调用当中,我都传入了ERROR_DEPTH函数的返回值。

代码清单 4: backtrace_to 函数调用了 UTL_CALL_STACK 子程序

SQL> CREATE OR REPLACE FUNCTION backtrace_to

2     RETURN VARCHAR2

3  IS

4  BEGIN

5     RETURN

6        utl_call_stack.backtrace_unit (

7           utl_call_stack.error_depth)

8        || ' line '

9        ||

10        utl_call_stack.backtrace_line (

11           utl_call_stack.error_depth);

12  END;

13  /

SQL> CREATE OR REPLACE PACKAGE pkg1

2  IS

3     PROCEDURE proc1;

4     PROCEDURE proc2;

5  END;

6  /

SQL> CREATE OR REPLACE PACKAGE BODY pkg1

2  IS

3     PROCEDURE proc1

4     IS

5        PROCEDURE nested_in_proc1

6        IS

7        BEGIN

8           RAISE VALUE_ERROR;

9        END;

10     BEGIN

11        nested_in_proc1;

12     END;

13

14     PROCEDURE proc2

15     IS

16     BEGIN

17        proc1;

18     EXCEPTION

19        WHEN OTHERS THEN RAISE NO_DATA_FOUND;

20     END;

21  END pkg1;

22  /

SQL> CREATE OR REPLACE PROCEDURE proc3

2  IS

3  BEGIN

4     pkg1.proc2;

5  END;

6  /

SQL> BEGIN

2     proc3;

3  EXCEPTION

4     WHEN OTHERS

5     THEN

6        DBMS_OUTPUT.put_line (backtrace_to);

7  END;

8  /

HR.PKG1 line 19

注意,错误回溯堆栈中的深度值和调用堆栈的深度值不同。对调用堆栈而言,1是堆栈的顶部(当前执行的子程序)。对错误回溯堆栈去而言,我的代码出错之处是用ERROR_DEPTH找到的,而不是1。

有了UTL_CALL_STACK,我不再需要解析完整的回溯字符串,而用DBMS_UTILITY.FORMAT_ERROR_BACKTRACE就不得不这么做。相反,我可以精确地发现,显示并且记录我所需要的关键信息。

关于UTL_CALL_STACK要记住的有几点:

编译器的优化可能会改变词汇,动态和回溯的深度,因为优化过程可能意味着子程序调用被跳过。

如果越过了远程调用的边界,UTL_CALL_STACK就不被支持。例如,proc1 调用远程过程remoteproc2,那么remoteproc2利用UTL_CALL_STACK将得不到proc1的相关信息。

词汇单元的信息不是通过UTL_CALL_STACK来得到的。你可以利用PL/SQL的条件编译来得到该信息。

UTL_CALL_STACK是非常方便的工具,但是在现实世界中,你可能需要在这个包的子程序之外再建立一些自己的工具代码。我创建了一个帮助包,里面有些工具,我想你可能会觉得有用。你可以在12c_utl_call_stack_helper.sql 和 12c_utl_call_stack_helper_demo.sql文件中找到代码。

http://www.oracle.com/technetwork/issue-archive/2014/14-jan/o14plsql-2041787.zip

更好的诊断,更好的编程

三个DBMS_UTILITY函数(DBMS_UTILITY.FORMAT_CALL_STACK, DBMS_UTILITY.FORMAT_ERROR_STACK, 和 DBMS_UTILITY.FORMAT_ERROR_ BACKTRACE) 一直都是PL/SQL代码中诊断和解决问题的好帮手。UTL_CALL_STACK包认识到这个数据的重要性,往前跨出了一大步,给了PL/SQL开发者访问更多的深层的有用的信息的途径。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值