背景
我使用了一些Oracle文章来开发一个错误包,其中包含五个过程。
其中两个是 Log_And_Return 和 Log_And_Continue 。整个计划都会调用它们。每个都接受输入并将其传递给 Handle 过程。例如:
PROCEDURE Log_And_Return (error_name)
IS
BEGIN
Handle (error_name, TRUE, TRUE);
END Log_And_Return;
然后Handle 过程调用 Log 过程和 Raise_To_Application 过程,具体取决于传递给它的变量,如下所示:
PROCEDURE Handle (error_name, log_error, reraise_error)
IS
BEGIN
// Code to fetch error code and message using error_name input parameter.
IF log_error THEN
LOG (error_code, error_message);
END IF;
IF in_reraise_error THEN
Raise_To_Application (error_code, error_message);
END IF;
END Handle;
日志过程存储日期,堆栈跟踪,错误代码,错误消息和ID,最后 Raise_To_Application 过程执行以下操作:
raise_application_error (error_code, error_message);
问题:
我的问题是这个。我们假设我有一个执行查询的程序,例如获取客户记录。如果此查询失败,则这是一个大问题。所以我可以这样做:
BEGIN
SELECT *something*
FROM *some table*
WHERE *some field* = *some user input*
// more logic
EXCEPTION
WHEN NO_DATA_FOUND THEN
ERR.Log_And_Return('unknown_id');
WHEN OTHERS THEN
ERR.Log_And_Return('unknown_error');
END;
在这里,我的 Log_And_Return 过程接受输入,转到表并返回一个字符串以显示给用户。如果查询未找到用户的记录,并且未知错误的一般错误,我会发出特定错误。在这两种情况下,都会执行日志记录,这会记录错误的完整堆栈跟踪。
然而,在我的例子中,我有一个更多的逻辑"部分。让我们说,我将代码修改为:
BEGIN
SELECT *something* INTO *some variable*
FROM *some table*
WHERE *some field* = *user id*
Call_Another_Procedure(*user id*, *some variable*)
EXCEPTION
WHEN NO_DATA_FOUND THEN
ERR.Log_And_Return('unknown_id');
WHEN OTHERS THEN
ERR.Log_And_Return('unknown_error');
END;
现在,在select查询之后,我用select查询的结果调用另一个过程。在 new 查询中,我做了一些事情,包括 update 语句,如下所示:
// bunch of logic
BEGIN
UPDATE *another table*
SET *some field* = *some value*
WHERE *some field* = *variable passed into method*
EXCEPTION
WHEN NO_DATA_FOUND THEN
Err.Log_And_Return('some_error')
END;
问题:
我的问题是,如果查询没有返回结果,我会抛出 NO_DATA_FOUND 错误,我记录问题,然后在我的" Raise_To_Application"中引发应用程序错误程序......然后将被"当其他人"父过程中的子句,它将向用户返回错误的消息。
这是什么解决方法?注意:如果需要发布更多代码,请告诉我。
修改
我考虑过的一个解决方法,我不知道是否建议这样做,将包含每个存储过程的BEGIN END EXCEPTION块,其中每个过程都有一个"当其他&#34 34;刚刚记录并重新记录最新错误的块(即使用SQLCODE)。然后,在我的应用程序层中,我可以指定如果错误在-20000和-20999之间,显示它及其消息,否则显示一般消息(并且DBA可以通过查看日志找出数据库中发生的事情表,以及完整的堆栈跟踪)。有什么想法吗?
编辑2:
如果有什么不合理的话,我可以澄清一下。我已经对代码进行了大量更改和简化,以删除id参数和其他一些内容。
答案 0 :(得分:2)
这几乎是我一直在使用的方法,因为我想在我的代码中记录每个入口和出口点:
application_error EXCEPTION;
PRAGMA EXCEPTION_INIT (application_error, -20000);
BEGIN
SELECT *something* INTO *some variable*
FROM *some table*
WHERE *some field* = *user id*
Call_Another_Procedure(*user id*, *some variable*)
EXCEPTION
WHEN NO_DATA_FOUND THEN
ERR.Log_And_Return('unknown_id');
WHEN application_error THEN -- ordinary exception raised by a subprocedure
ERR.Log_And_Return('application_error');
RAISE;
WHEN OTHERS THEN
ERR.Log_And_Return('unknown_error');
RAISE;
END;
对于子程序:
BEGIN
UPDATE *another table*
SET *some field* = *some value*
WHERE *some field* = *variable passed into method*
EXCEPTION
WHEN NO_DATA_FOUND THEN
Err.Log_And_Return('some_error'); -- this raises ORA-20000
END;
答案 1 :(得分:1)
对于when_others例外考虑使用AFTER SERVERERROR triggers。像贝洛一样的东西
create or replace trigger TRG_SERVERERROR
after servererror on database
declare
<some_variable_for_logging_the_call_stack>
begin
ERR.Log;
end;
我会引用Tom Kytes的话,他被允许提交PL / SQL新功能的三个请求,这就是他所说的
我抓住机会。我的第一个建议很简单,“删除 WHEN OTHERS语言中的条款。“
您还可以阅读Tom Kyte - Why You Really Want to Let Exceptions Propagate
中的以下文章UPD:您案例中解决方案的整个工作流程如下(以我的主观意见)
我建议不要包括其他人。我更喜欢接收不友好的错误消息,而不是无缝消息 - 类似于&#34; Ooops,出错了。&#34;。在一天结束时,您还可以将所有意外的异常包装到应用程序层上的用户的某些消息中,并包装有关数据库的详细信息,以供第三方使用,等等。
我的建议是有一些ERR。
create or replace package ERR
ci_NoDataFound constant int := -20100;
NoDataFound exception;
pragma exception_init(NoDataFound, -20100);
procedure Raise;
procedure Log;
end P_PRSRELIAB;
在您的父程序中,您将处理当前特定程序的激活,而不处理其他程序。
BEGIN
SELECT *something* INTO *some variable*
FROM *some table*
WHERE *some field* = *user id*
Call_Another_Procedure(*user id*, *some variable*)
EXCEPTION
WHEN NO_DATA_FOUND THEN
ERR.Raise(-20100, 'unknown user id');
END;
从父母调用的过程将只处理此特定过程的激活。
BEGIN
SELECT *something*
FROM *some table*
WHERE *some field* = *some user input*
EXCEPTION
WHEN NO_DATA_FOUND THEN
ERR.Raise(-20100, 'unknown some user input');
END;
在应用程序层,我们将有适当的消息 - &#34; uknown一些用户输入&#34;或&#34;未知的用户ID&#34;。另一方面,触发器将记录有关特定异常的所有信息。
答案 2 :(得分:0)
您需要使用RAISE
重新引发基础过程中的错误。
当error
发生时,如果您有exception block
,则句柄会移至exception block
。在您使用caller
re-raise
之前,RAISE
仍然不会发现。
将所有基础程序保留在BEGIN-END
块中。
另外,使用dbms_utility.format_error_stack
和dbms_utility.format_error_backtrace
来获取调用堆栈。