所有DML的其他异常处理时我需要吗?

时间:2015-02-13 20:31:07

标签: sql oracle plsql exception-handling

我对在Oracle中处理未知异常的最佳实践感到困惑。

我可以这样做:

BEGIN
    --do something
EXCEPTION
WHEN NO_DATA_FOUND THEN
    raise_application_error etc
WHEN OTHERS THEN
    raise;
END;

这似乎是在一些博客和网站上推荐的,甚至在Oracle documentation中讨论过:

  

通过包含OTHERS异常处理程序来避免未处理的异常   每个PL / SQL程序的最高级别。

     

将OTHERS异常处理程序中的最后一个语句设为RAISE   或调用RAISE_APPLICATION_ERROR程序。 (如果你这样做   不遵循这种做法,并启用PL / SQL警告,然后启用   获取PLW-06009。)有关RAISE或调用的信息   RAISE_APPLICATION_ERROR,请参阅“明确提出异常”。

但是我也知道在一些地方已经提到这是相当可怕的,例如Ask Tom

  

我真的希望我们甚至不支持其他人。

     

您应该只捕获您期望和可以做的例外   一些有关。让其他人传播出来,这样你就可以发现它们   (所以你看到了他们)

所以我的问题很简单:

我需要一个当其他子句记录并提升每次,单次我有一些使用数据操作语言(例如插入/更新/删除)?如果没有,我什么时候想避开它?

4 个答案:

答案 0 :(得分:6)

与任何事情一样,取决于。

通常,我的偏见是仅捕获您可以处理的异常,或者您可以添加其他信息/上下文。例如,如果您知道有SELECT INTO可能返回0行,那么如果您可以提供合理的默认值并继续运行,则处理no_data_found异常是有意义的。如果您可以向异常添加其他上下文,通常是通过使错误消息的文本更有意义(“找不到客户”而不是“找不到数据”)或通过包含诸如本地变量的值之类的内容来提供帮助用于调试。

设计代码可能是有意义的,这样你总是有一个WHEN OTHERS异常处理程序捕获意外的异常,将它们与适当的上下文(局部变量的值,将它们记录到表(或文件)中,例如),然后重新抛出。如果你一直这样做,你最终会得到一些非常详细的错误记录,它会在抛出意外异常时提供有关程序状态的大量信息。不幸的是,在绝大多数情况下,实施和维护这些系统的团队在此过程中失去了他们的纪律,WHEN OTHERS的使用导致可维护系统的维护要少得多。

如果您的通用WHEN OTHERS不以RAISE(或RAISE_APPLICATION_ERROR)结尾,那么您的代码将默默地吞下异常。来电者不会知道出了什么问题,并且会继续认为一切都好。但是,不可避免的是,未来的某些步骤将失败,因为较早的静默故障使系统处于意外状态。如果在大块的末尾有一个WHEN OTHERS,它有几十个SQL语句而只有一个通用RAISE,那么你将丢失有关实际错误发生在哪一行的信息。

答案 1 :(得分:1)

在这些方案中捕获特定层的所有未处理的异常可能是适当的(可能不是完整列表):

  • 您想记录异常,然后重新抛出它。
  • 您想要使用更具体的上下文错误消息重新抛出它。例如,您可能希望提供一条消息,提供诸如传递给过程的参数等信息。
  • 您想隐藏来电者的详细信息。可能由于安全问题而且希望确保应用程序无法访问可能泄露敏感详细信息的真实异常。

如果从应用程序调用这些过程,最好让它们全部冒泡到应用程序,让应用程序决定何时/何时处理/记录/包装它们。

通常应用程序采用类似的技术。它通常具有所有未处理异常的处理程序,记录完整的异常/堆栈,然后将它们包装在一般错误中以显示给用户,从而隐藏原始错误中的潜在敏感信息,并为用户提供更具体的方向,如“如果错误仍然存​​在,请联系支持部门。”

在这里,您可能会为应用程序员带来麻烦: 您在SP层捕获异常,然后重新抛出一般错误。虽然最好是进行防御性编码并避免异常,但有时应用程序员别无选择,只能逐字try,知道在某些情况下会发生异常,然后专门编写代码来处理它。如果将异常包装在一般异常中,那么程序员无法解决特定的错误情况,因为您已将它们全部隐藏在同一个存储桶中。此外,应用程序级别的日志通常包含完整的堆栈跟踪,并且在最深层次上将是从数据库调用中冒出的错误,该错误将包含在您的通用错误中,从而隐藏问题的真正原因是。在尝试解决难以重现的错误时,这可能是一个巨大的问题,而且您确实需要详细的日志,以便您查看真正的错误,以便了解问题可能是什么。

当然并非所有应用程序员都会这样想,因为他们并不都采用相同的技术。但是,任何体面的程序员都应该知道如何以通用方式包装来自数据库的错误,如果这是他们选择做的。另一方面,解开异常通常更难或不可能,这取决于原始包装时省略的内容。这就是为什么IMO最好在没有包装异常的情况下犯错,直到你处于与用户交互的层为止。

答案 2 :(得分:0)

虽然Tom Kyte是一位对甲骨文有着丰富知识的杰出人士,但在橡胶遇到困难的道路上,他仍然只是一名DBA。因此,虽然我永远不会想到与调整队列或表空间的最佳布局或解决死锁的最佳方法的问题,但他的编程话语可以随意采取。或者两个。

为什么我们想要愚弄异常呢?在回答这个问题时,我们必须认识到,例外分为两大类:

  1. 我们期望的例外情况。通常,我们称之为系统或用户例程引发的异常。
  2. 我们不期望的例外情况。通常,当某些原始操作出错时系统引发的异常 - 例如除以零。
  3. 当我们设计任何类型的数据库存储过程(甚至是Java或C#中的类方法)时,我们的代码可能需要正常工作的某些最小输入和函数状态。如果调用者没有提供足够的输入或错误的输入类型,或者没有,例如,打开正确的通道到某些I / O流,则代码可能无法运行,并且不能忽略此故障。当发生这种情况时,我们希望让来电者知道发生了什么,以便他们采取纠正措施。

    现在我们可能不知道哪个代码调用我们的代码,或者在什么情况下或者我们无法操作会对它自己的操作产生什么影响。因此,我们提出了一个有意义的异常,允许它以开发人员认为合适的方式进行响应。

    对于我们的日常工作而言,我们所称的任何例程也是如此。让我们来看一个创建一个具有指定名称和结构的表的过程的简单示例(在通用SQL语法中)。

    procedure CreateTable( varchar tableName, varchar fieldDefs ) as begin
        DropTable( tableName );
        exec immediate 'create table ' || tableName || '(' || fieldDefs || ');';
    end procedure;
    

    可能出现什么问题?首先,tableName参数可以为null,或者它包含的字符串不能正确形成合法的表名,或者可能已经存在该名称的表,或者用户没有创建表的权限。其次,fieldDefs参数也可以为null,或者它包含的字符串不能正确形成字段定义。

    我们是否必须花费前几行代码检查所有这些可能性。也许。至少,检查一个null必需参数并引发一个有意义的异常会很好。

    procedure CreateTable( varchar tableName, varchar fieldDefs ) as begin
        if tableName is null or fieldDefs is null then
            raise NULL_ARGUMENT;
        end if
        DropTable( tableName );
        exec immediate 'create table ' || tableName || '(' || fieldDefs || ');';
    end procedure;
    

    现在我们的例程调用另一个例程。我们知道我们传递给它的论点不是空的,但可能还有其他问题。或者表格可能不存在。如果该表不存在,我们想要忽略该异常,但如果有关于参数内容的伪造,我们将发现当我们使用它来创建表时,涉及表创建的错误消息将更有意义。例程名为" CreateTable"然后涉及丢桌的事情。所以我们也选择忽略这些。这是others派上用场的地方。

    procedure CreateTable( varchar tableName, varchar fieldDefs ) as begin
        if tableName is null or fieldDefs is null then
            raise NULL_ARGUMENT;
        end if
        begin
            DropTable( tableName );
        exception
            when others then
               ; -- ignore
        end;
        exec immediate 'create table ' || tableName || '(' || fieldDefs || ');';
    end procedure;
    

    现在我们想要一个异常处理程序来检查上面提到的每个可能的失败源,它们必须来自create table语句(或我们自己的NULL_ARGUMENT)并将我们自己有意义的异常提交回调用者

    但这远远超出了我们的目的。让我们来看看如果没有when other选项可以做什么。我们必须测试每个可以想象的异常只是为了忽略它们

    when this then
        ; --ignore
    when that then
        ; --ignore
    when something then
        ; --ignore
    when something_else then
        ; --ignore
    when something_else_entirely then
        ; --ignore
    when one_in_a_million_longshot then
        ; --ignore
    

    至少可以说这很麻烦。即使我们想要捕获一些特定的异常并为了清晰而重新提升我们自己,但忽略其余的,我们仍然必须将这个巨大的列表的大部分附加到每个异常处理程序。我们有没有错过一个?让我们不希望因为如果我们这样做,并且它被提出,我们就会放松对整个异常处理操作的控制。

    不,尽管您可能已经听过,但是能够指定我们想要处理"所有其他迄今为止未指明的例外"减轻了太多的编程问题,可以忽略不计。

    可以滥用吗?当然。任何语言的任何功能和任何工具箱中的任何工具都可能被滥用。这是一个剥夺功能或工具的脆弱借口。

答案 3 :(得分:0)

当正确使用时,其他人肯定是pl / sql的重要组成部分。一般情况下,我不建议每个DML使用WHEN OTEHRS。相反,我们使用'func_no'跟踪我们在程序中的位置 - 一个简单的硬编码值,随着每个DML和代码中的其他关键点而变化。我们还会跟踪程序中更改的有用信息。这不仅仅是 - '创建发票。',而是“创建发票,客户='|| rec.customer_number ||',发票号码:'|| to_char(invoice_number);

我的建议是使用NO_DATA_FOUND,TOO_MANY_ROWS,DIVIDE_BY_ZERO等明确处理预期的数据条件,并使用SQL%ROWCOUNT等对数据进行测试。

我每个节目单位只使用1个等等。它位于外部区域。它包含func_no和特定于数据的消息以及错误堆栈和回溯,通常会进行回滚,并始终执行RAISE;