【问题标题】:when others then exception handling当其他人则异常处理
【发布时间】:2015-01-18 09:31:27
【问题描述】:

背景:

我使用了几篇 Oracle 文章开发了一个错误包,它由五个过程组成。

其中两个是 Log_And_ReturnLog_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;

现在,在选择查询之后,我正在使用选择查询的结果调用另一个过程。在这个 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 块包装每个存储过程,其中每个过程都有一个刚刚记录的“When Others”块和重新引发了最近的错误(即使用 SQLCODE)。然后,在我的应用程序层中,我可以指定如果错误在 -20000 和 -20999 之间,则将其与消息一起显示,否则显示一般消息(DBA 可以通过查看日志来找出数据库中发生的情况表,以及完整的堆栈跟踪)。对此有什么想法吗?

编辑 2:

如果有什么不明白的地方,我可以澄清一下。我对代码进行了大量更改和简化,以删除 id 参数和其他一些内容。

【问题讨论】:

标签: oracle exception stored-procedures plsql


【解决方案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:在您的案例中,解决方案的整个工作流程如下(我的主观意见)

我建议在其他情况下不包括在内。我更喜欢收到不友好的错误消息,而不是无缝的消息——比如“糟糕,出了点问题。”。归根结底,您还可以在应用程序层为用户包装某些消息的所有意外异常,并包装有关数据库的详细信息,不被第三方使用等。

我的建议是有一些错误。

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;

在应用层,我们将有适当的消息——“未知用户输入”或“未知用户 id”。另一方面,触发器将记录有关特定异常的所有信息。

【讨论】:

  • 现在,目前我的 Log_And_Return 过程执行此日志记录,然后引发应用程序错误。那么我可以创建一个触发器来简单地检测是否抛出了应用程序错误,如果是,则返回到应用程序层(即它不需要担心日志记录,因为这已经完成了)?
  • 你可以有一个 LOG 过程,它只会插入日志值(不提升)。从 db 引发的异常将返回到调用者实例。
  • 再一次,我已经在这样做了。我的“Log And Return”调用 Log 过程,然后引发应用程序错误。那么我是否希望获得一个触发器来检测何时引发应用程序错误并强制返回到应用程序层?
  • 从父过程中删除所有 when others 子句。当您从子过程中引发 NO_DATA_FOUND 异常时,它将返回到您的应用程序层(触发器将记录它 - 两次,一次在“记录并返回”过程中,一次在触发器端),所以基本上你可以删除您的异常处理过程中的日志记录部分,并将所有日志记录留在触发器部分。请编辑您的评论并删除“再次”部分。谢谢。
  • 编辑评论太晚了。更重要的是,你会建议在其他人的时候删除 ALL 吗?简单地让应用层处理任何未知的异常?对吗?
【解决方案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;

【讨论】:

  • 啊,所以你对每个用户定义的错误都使用 -20000?
  • 我想如果你这样做,在你的第一个代码块中你不想在“application_error”下再次调用 Err.Log_And_Return,而只是向应用程序层提出错误(否则你会记录两次)。对吗?
  • 是的。我不知道你的记录器是做什么的;我的有一些智能让我可以跟踪执行层次结构,这就是为什么我总是将每个退出记录到异常处理程序之外。
  • Mine 将堆栈跟踪以及用户 ID、日期和其他一些内容写入日志。我正在考虑您回答中的方法,我可以将我当前的数字系统从 -20999 更改为 -20000,以解决所有错误。相反,我可以在代码 -20000 下出现所有错误,但在单独的表中为我的所有用户错误提供一个唯一编号,例如从 1 到 1000。然后我可以为所有错误设置一个唯一错误代码,但也是一种简单的方法通过程序跟踪它们。听起来不错?
  • 好的,但重要的是要确保您的 WHEN OTHERS 处理程序始终执行 RAISE;所以他们不会改变异常。
【解决方案3】:

您需要使用RAISE 重新引发底层过程中的错误。

error 出现时,如果您有exception block,句柄将移动到exception blockcaller 将保持不知道,直到您使用 re-raise RAISE

将所有底层过程保留在BEGIN-END 块内。

另外,使用dbms_utility.format_error_stackdbms_utility.format_error_backtrace 来获取调用堆栈。

【讨论】:

  • 也许我的问题不清楚。我已经在做这一切了。我的 Log_And_Return 方法使用完整的堆栈跟踪记录问题,然后执行 raise_application_error。这是事实,当它被提出时,调用者过程中的其他块将改变输出消息
  • 不,@Lalit 是正确的。如果你必须有一个 WHEN OTHERS 触发器,你可以记录错误,但你应该用一个简单的RAISE; 结束它。这样,真正的、不可预测的、意外的未处理异常会被引发给调用者。您的目标是为所有预测的、预期的异常添加处理程序。
  • @Andrew Martin,您只需要在底层过程中简单地使用 RAISE,这样当发生错误时,它就会被引发并提供给调用者。这是主要程序。否则,您最终会多次记录错误并使自己难以调试。
  • 是的,但我想要不同的错误消息,具体取决于错误发生的位置,所以我不能只是提出问题。我想提出一个特定的错误。如果我使用特定错误引发它,那么一旦它遇到调用程序的“当其他人”块,该错误将更改为那里声明的任何错误 - 除非我只是重新引发所有“当其他人”错误。
猜你喜欢
  • 2018-01-04
  • 2014-11-03
  • 1970-01-01
  • 1970-01-01
  • 2012-10-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-16
相关资源
最近更新 更多