[翻译]Oracle 12c数据库中的UTL_CALL_STACK包

[复制链接]
查看11 | 回复9 | 2008-9-15 01:28:12 | 显示全部楼层 |阅读模式
本帖最后由 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
2IS
3BEGIN
4 DBMS_OUTPUT.put_line (DBMS_UTILITY.format_call_stack);
5END;
6/
SQL> CREATE OR REPLACE PACKAGE pkg1
2IS
3 PROCEDURE proc2;
4END pkg1;
5/
SQL> CREATE OR REPLACE PACKAGE BODY pkg1
2IS
3 PROCEDURE proc2
4 IS
5 BEGIN
6proc1;
7 END;
8END pkg1;
9/
SQL> CREATE OR REPLACE PROCEDURE proc3
2IS
3BEGIN
4 FOR indx IN 1 .. 1000
5 LOOP
6NULL;
7 END LOOP;
8
9 pkg1.proc2;
10END;
11/
SQL> BEGIN
2 proc3;
3END;
4/
——————— PL/SQL Call Stack ———————
object handleline 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
2IS
3BEGIN
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
12DBMS_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;
28END;
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
2IS
3 PROCEDURE do_stuff;
4END;
5/
SQL> CREATE OR REPLACE PACKAGE BODY pkg
2IS
3 PROCEDURE do_stuff
4 IS
5PROCEDURE np1
6IS
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;
17BEGIN
18 np2;
19END;
20 BEGIN
21np1;
22 END;
23END;
24/
SQL> BEGIN
2 pkg.do_stuff;
3END;
4/
LexDepthDepth LineNo Name
——————— ——————— ———————— ——————————————————————————
0 6 2
__anonymous_block
1 521
PKG.DO_STUFF
2 418
PKG.DO_STUFF.NP1
3 315
PKG.DO_STUFF.NP1.NP2
4 212
PKG.DO_STUFF.NP1.NP2.NP3
0 112
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
3IS
4BEGIN
5 RETURN
6utl_call_stack.backtrace_unit (
7 utl_call_stack.error_depth)
8|| ' line '
9||
10utl_call_stack.backtrace_line (
11 utl_call_stack.error_depth);
12END;
13/
SQL> CREATE OR REPLACE PACKAGE pkg1
2IS
3 PROCEDURE proc1;
4 PROCEDURE proc2;
5END;
6/
SQL> CREATE OR REPLACE PACKAGE BODY pkg1
2IS
3 PROCEDURE proc1
4 IS
5PROCEDURE nested_in_proc1
6IS
7BEGIN
8 RAISE VALUE_ERROR;
9END;
10 BEGIN
11nested_in_proc1;
12 END;
13
14 PROCEDURE proc2
15 IS
16 BEGIN
17proc1;
18 EXCEPTION
19WHEN OTHERS THEN RAISE NO_DATA_FOUND;
20 END;
21END pkg1;
22/
SQL> CREATE OR REPLACE PROCEDURE proc3
2IS
3BEGIN
4 pkg1.proc2;
5END;
6/
SQL> BEGIN
2 proc3;
3EXCEPTION
4 WHEN OTHERS
5 THEN
6DBMS_OUTPUT.put_line (backtrace_to);
7END;
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开发者访问更多的深层的有用的信息的途径。

回复

使用道具 举报

千问 | 2008-9-15 01:28:12 | 显示全部楼层
善哉。
回复

使用道具 举报

千问 | 2008-9-15 01:28:12 | 显示全部楼层
辛苦了!


回复

使用道具 举报

千问 | 2008-9-15 01:28:12 | 显示全部楼层
多谢


回复

使用道具 举报

千问 | 2008-9-15 01:28:12 | 显示全部楼层
哈哈,"范例",有那么点老夫聊发少年狂嘛
回复

使用道具 举报

千问 | 2008-9-15 01:28:12 | 显示全部楼层
steven老人家写的,谁敢说不是范例?
回复

使用道具 举报

千问 | 2008-9-15 01:28:12 | 显示全部楼层
newkid 发表于 2014-11-8 00:29
steven老人家写的,谁敢说不是范例?

我还以为这个范例是定义在“翻译”上的,嘿嘿。我这个级别的plsql,压根不知道Steven Feuerstein是谁。。。现查了下
回复

使用道具 举报

千问 | 2008-9-15 01:28:12 | 显示全部楼层
最后一行有个别字 ,应该是“重要性”
回复

使用道具 举报

千问 | 2008-9-15 01:28:12 | 显示全部楼层
lastwinner 发表于 2014-12-2 12:41
最后一行有个别字 ,应该是“重要性”

改过来了。
回复

使用道具 举报

千问 | 2008-9-15 01:28:12 | 显示全部楼层
标记 收藏先。。。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

主题

0

回帖

4882万

积分

论坛元老

Rank: 8Rank: 8

积分
48824836
热门排行