Oracle PL/SQL Programming 第6章:Exception Handlers 读书笔记

总的目录和进度,请参见开始读 Oracle PL/SQL Programming 第6版

无论如何努力,您的程序中总会再出现一个错误。但我们仍应实现调试和保护我们的程序。PL/SQL 提供了一种强大而灵活的方法来捕获和处理错误。

Exception-Handling Concepts and Terminology

在 PL/SQL 中,任何类型的错误都被视为例外:程序中不应发生的异常情况。 例外可能是以下之一:

  • 系统生成的错误(例如"内存不足"或"索引中的值重复")
  • 由用户操作引起的错误
  • 应用程序向用户发出的警告

PL/SQL 使用异常处理程序体系结构捕获并响应错误。 异常处理程序机制允许您将错误处理代码与可执行语句清晰地分开 。 它还提供了一个事件驱动模型(与线性代码模型相反)来处理错误。 换句话说,无论异常如何引发,它都会由相同的异常处理程序进行处理。

当 PL/SQL 中的错误引发异常时,当前 PL/SQL 块的执行部分中的处理暂停,并且控制权转移到当前块的异常处理部分(如果存在)。 处理完异常后,您无法返回到该块。 相反,控制权将传递给封闭块(如果有)。

有两种类型的例外:

  • 系统异常

    由 Oracle 定义的异常,通常由 PL/SQL 运行时引擎在检测到错误情况时引发。 一些系统异常有名称,例如 NO_DATA_FOUND,而许多其他系统异常只有数字和描述。

  • 程序员定义的异常

    特定于当前的应用程序。 您可以使用 EXCEPTION_INIT pragma(编译器指令,请求特定行为)将异常名称与特定 Oracle 错误关联起来,也可以使用 RAISE_APPLICATION_ERROR 为该错误分配编号和描述。

本章将使用以下术语:

  • 异常部分

    这是 PL/SQL 块(包的匿名块、过程、函数、触发器或初始化部分)中的可选部分,包含一个或多个异常"处理程序"。 异常部分的结构与第 4 章中讨论的 CASE 语句的结构非常相似。

  • 引发异常

    您可以通过向运行时引擎通知错误来停止当前 PL/SQL 块的执行。 数据库本身可以引发异常,或者您自己的代码可以使用 RAISE 或 RAISE_APPLICATION_ERROR 命令引发异常。

  • Handle(用作动词)、handler(用作名词)

    您可以通过将错误"捕获"到异常部分来处理错误。 然后,您可以在处理程序中编写代码来处理该错误,这可能涉及在日志中记录错误的发生、向用户显示消息或将异常传播到当前块之外。

  • 范围

    这是指可以引发异常的代码部分(无论是在特定块中还是在整个会话中)。 此外,异常部分可以捕获和处理引发的异常的那部分代码。

  • 传播

    这是一个过程,如果该块中的异常未得到处理,则异常从一个块传递到其封闭块。

  • 未处理的异常

    当异常传播而没有在最外层 PL/SQL 块之外进行处理时,我们称该异常"未处理"。 然后控制权传回主机执行环境,此时环境/程序确定如何响应异常(回滚事务、显示错误、忽略它等)。

  • 未命名或匿名异常

    这是一个异常,具有与其关联的错误代码和描述,但没有可在 RAISE 语句或异常处理程序 WHEN 子句中使用的名称。

  • 命名异常

    这是指由 Oracle 在其内置包之一中或由开发人员指定名称的异常。 您还可以通过使用 EXCEPTION_INIT 编译指示将名称与错误代码关联起来,或者仅通过其编号进行定义(可用于引发和处理异常)。

Defining Exceptions

在引发或处理异常之前,必须先对其进行定义。 Oracle 预定义了数千种异常,主要是通过为这些异常分配编号和消息来实现的。 Oracle 还为这数千个中的相对少数指定了名称:最常遇到的异常。

这些名称在 STANDARD 包(PL/SQL 中的两个默认包之一;DBMS_STANDARD 是另一个)以及其他内置包(例如 UTL_FILE 和 DBMS_SQL)中分配。 Oracle 用于定义 NO_DATA_FOUND 等异常的代码与您为定义或声明自己的异常而编写的代码相同。 您可以通过两种不同的方式来执行此操作,如以下部分所述。

Declaring Named Exceptions

PL/SQL 在 STANDARD 包(和其他内置包)中声明的异常涵盖内部或系统生成的错误。 然而,用户在应用程序中遇到(或引起)的许多问题都是特定于该应用程序的,应该由您的程序妥善处理。

要处理异常,您必须有该异常的名称。 由于 PL/SQL 无法为您命名这些异常(它们特定于您的应用程序),因此您必须通过在 PL/SQL 块的声明部分中声明异常来自行命名。

sql 复制代码
exception_name EXCEPTION;

异常的名称只能通过两种方式引用:

  • 在程序执行部分的 RAISE 语句中(引发异常),如下所示:
sql 复制代码
raise exception_name;

在异常部分的 WHEN 子句中(处理引发的异常),如下所示:

sql 复制代码
WHEN exception_name THEN

Associating Exception Names with Error Codes

只有少数异常被命名,其余数以千计的异常仅由错误号和消息来定义。 开发人员可以使用 RAISE_APPLICATION_ERROR引发异常,该异常仅包含错误号(介于 -20000 和 -20999 之间)和错误消息。

没有名称的异常是完全合法的,但它们可能会导致代码难以阅读和维护。例如,假设我编写了一个程序,其中我知道数据库可能会引发与日期相关的错误,例如 ORA-01843:不是有效的月份。 我可以编写一个异常处理程序来捕获该错误,但代码非常晦涩:

sql 复制代码
EXCEPTION
   WHEN OTHERS THEN
      IF SQLCODE = −1843 THEN

注:SQLCODE 是一个内置函数,它返回最后引发的错误的编号。

Using EXCEPTION_INIT

EXCEPTION_INIT 是一个编译时命令或编译指示,用于将名称与内部错误代码关联。 EXCEPTION_INIT 指示编译器将声明为 EXCEPTION 的标识符与特定错误号相关联。 关联后,您可以按名称引发该异常并编写一个显式的 WHEN 处理程序来捕获错误。

使用 EXCEPTION_INIT,我可以将上一个示例中显示的 WHEN 子句替换为如下内容:

sql 复制代码
...
   invalid_month EXCEPTION;
   PRAGMA EXCEPTION_INIT (invalid_month, −1843);
   ...
EXCEPTION
   WHEN invalid_month THEN

一个有意义的名字比硬编码的错误号更容易理解,并可自解释。

PRAGMA EXCEPTION_INIT 必须出现在块的声明部分,并且指定的异常必须已在同一块、封闭块或包规范中定义。

sql 复制代码
DECLARE
   exception_name EXCEPTION;
   PRAGMA EXCEPTION_INIT (exception_name, integer);

错误号可以是任何整数值,但具有以下限制:

  • 它不能是−1403(NO_DATA_FOUND 的两个错误代码之一)。 如果出于某种原因您想要将自己的命名异常与此错误相关联,则需要将 100 传递给 EXCEPTION_INIT pragma。
  • 不能为 0 或 100 以外的任何正数。
  • 它不能是小于-1000000 的负数。

此编译指示在两种情况下最有用:

  • 为您在代码中通常引用的匿名系统异常命名,换句话说,当 Oracle 没有为错误预定义名称并且您只有可用的编号时。
  • 使用 RAISE_APPLICATION_ERROR 为您引发的特定于应用程序的错误分配名称。 这允许您通过名称而不是简单地通过数字来处理此类错误。

在这两种情况下,我建议您将 EXCEPTION_INIT 的使用在包中集中定义,以便定义不会分散在整个代码中。引用时带上包名即可:

sql 复制代码
WHEN package_name.invalid_identifier THEN ...

About Named System Exceptions

Oracle 通过在内置包规范中包含 EXCEPTION_INIT pragma 语句来为相对少量的系统异常命名。

最重要和最常用的命名异常集可以在 PL/SQL 的 STANDARD 包中找到。 由于此包是 PL/SQL 的两个默认包之一,因此您可以引用这些异常,而无需包含包名称作为前缀。 例如,对于 NO_DATA_FOUND 异常,下面2个语句是等效的:

sql 复制代码
1. WHEN NO_DATA_FOUND THEN
2. WHEN STANDARD.NO_DATA_FOUND THEN

对于非默认包,则必须带包名作为前缀:

sql 复制代码
WHEN DBMS_LOB.invalid_argval THEN...

PL/SQL 预定义的例外可以参考链接11.4 Predefined Exceptions

Scope of an Exception

例外的范围是该例外"涵盖"的代码部分。 如果可以在该块中提出一个例外,则涵盖了一个代码块。 下表显示了每种不同类型异常的范围。

异常类型 范围描述
命名系统异常 这些例外是全局可用的,因为它们不在任何特定的代码中声明或局限于任何特定块。 您可以在任何块中发起并处理命名系统异常。
命名程序员定义的异常 这些例外只能在声明其声明的块(以及所有嵌套块)的执行和例外部分中提出和处理。 如果在软件包规范中定义了异常,则其范围是每个所有者都在该软件包上执行特权的程序。
匿名系统异常 这些例外可以通过WHEN OTHERS部分进行在任何PL/SQL异常部分中处理。 如果分配了一个名称,则该名称的范围与命名程序员定义异常的范围相同。
匿名程序员定义的异常 这些异常仅在调用RAISE_APPLICATION_ERROR时定义,然后将其传递回发起调用的程序。

Raising Exceptions

可通过三种方式引发异常:

  1. 数据库在检测到错误时
  2. 使用 RAISE 语句
  3. 使用 RAISE_APPLICATION_ERROR 过程

The RAISE Statement

3种形式:

sql 复制代码
-- 定义在当前块中的异常或系统异常
RAISE exception_name;
-- 定义在包package_name中的异常
RAISE package_name.exception_name;
-- 用在异常处理部分的WHEN子句中,引发相同的异常
RAISE;

Using RAISE_APPLICATION_ERROR

RAISE_APPLICATION_ERROR 是在DBMS_STANDARD 包中定义的内置过程,用来引发特定于应用程序的错误。 使用 RAISE_APPLICATION_ERROR 而不是 RAISE(也可以引发特定于应用程序的、显式声明的异常)的优点是您可以将错误消息与异常相关联。

运行此过程时,当前 PL/SQL 块的执行立即停止,并且对 OUT 或 IN OUT 参数(如果存在且没有 NOCOPY 提示)所做的任何更改都将被撤消。 对全局数据结构(例如包变量)和数据库对象(通过执行 INSERT、UPDATE、MERGE 或 DELETE)所做的更改将不会回滚。 您必须执行显式 ROLLBACK 才能反转 DML 操作的效果。

这是此过程的标头(在包 DBMS_STANDARD 中定义):

sql 复制代码
PROCEDURE RAISE_APPLICATION_ERROR (
   num binary_integer,
   msg varchar2,
   keeperrorstack boolean default FALSE);

其中 num 是错误号,必须介于 -20,999 和 -20,000 之间; msg 是错误消息,长度不得超过 2,000 个字符(超出部分将被忽略); keeperrorstack 指示是否要将错误添加到堆栈上的任何已存在错误 (TRUE) 或替换现有错误(默认值 FALSE)。

Handling Exceptions

一旦引发异常,当前 PL/SQL 块就会停止其常规执行并将控制权转移到异常部分。 然后,该异常要么由当前 PL/SQL 块中的异常处理程序处理,要么传递到封闭块。

要在引发异常后对其进行处理或捕获,必须为该异常编写异常处理程序。 在代码中,异常处理程序必须出现在程序中所有可执行语句之后、块的 END 语句之前。 EXCEPTION 关键字指示异常部分和各个异常处理程序的开始:

sql 复制代码
DECLARE
   ... declarations ...
BEGIN
   ... executable statements ...
[ EXCEPTION
   ... exception handlers ... ]
END;

异常处理程序的语法如下:

sql 复制代码
WHEN exception_name [ OR exception_name ... ]
THENexecutable statements

-- 或
WHEN OTHERS
THEN
   executable statements

您可以在单个异常部分中拥有多个异常处理程序。 异常处理程序的结构与条件 CASE 语句非常相似,例如:

sql 复制代码
EXCEPTION WHEN NO_DATA_FOUND THEN executable_statements1;
WHEN exception02 THEN executable_statements2;
WHEN OTHERS THEN executable_statements3; END;

WHEN 子句仅通过异常名称捕获错误,而不是通过错误代码捕获错误。 如果找到匹配项,则运行与该异常关联的可执行语句。 如果引发的异常未处理或与任何指定的异常不匹配,则将运行与 WHEN OTHERS 子句(如果存在)关联的可执行语句。 只有一个异常处理程序可以捕获特定错误。 执行该处理程序的语句后,控制立即传出块。

WHEN OTHERS 子句是可选的; 如果不存在,则任何未处理的异常都会立即传播回封闭块(如果有)。 WHEN OTHERS 子句必须是异常部分中的最后一个异常处理程序。 如果将任何其他 WHEN 子句放在 WHEN OTHERS 之后,您将收到以下编译错误:

sql 复制代码
PLS-00370: OTHERS handler must be last among the exception handlers of a block

Built-in Error Functions

以下为Oracle 提供的内置函数,这些函数可帮助您识别、分析和响应 PL/SQL 应用程序中发生的错误。

SQLCODE

SQLCODE 返回块中最近引发的异常的错误代码。 如果没有错误,SQLCODE 将返回 0。当您在异常处理程序之外调用 SQLCODE 时,它也会返回 0。

SQLERRM

SQLERRM 是一个返回特定错误代码的错误消息的函数。 如果不将错误代码传递给 SQLERRM,它将返回与 SQLCODE 返回的值关联的错误消息。

例如:

sql 复制代码
SQL> set serveroutput on
SQL> exec DBMS_OUTPUT.put_line (SQLERRM (-1403));
ORA-01403: no data found

PL/SQL procedure successfully completed.

其实和oerr输出是一致的:

bash 复制代码
$ oerr ora 1403
01403, 00000, "no data found"
// *Cause: No data was found from the objects.
// *Action: There was no data from the objects which may be due to end of fetch.

SQLERRM 返回的最大长度字符串是 512 字节。

DBMS_UTILITY.FORMAT_ERROR_STACK

与 SQLERRM 一样,返回与当前错误相关的消息(即 SQLCODE 返回的值)。 它与 SQLERRM 有两个不同之处:

  • 它将返回最多 1,899 个字符的错误消息,从而避免截断问题。
  • 您不能将错误代码号传递给此函数。

通常,您应该在异常处理程序逻辑中调用此函数来获取完整的错误消息。

请注意,即使函数的名称包含单词 stack,它也不会返回错误堆栈直到最初引发错误的行。 该工作属于 DBMS_UTILITY.FORMAT_ERROR_BACKTRACE。

DBMS_UTILITY.FORMAT_ERROR_BACKTRACE

Oracle 数据库 10g 中引入了该函数,它返回一个格式化字符串,该字符串显示程序堆栈和返回最初引发错误的行的行号。

您应该在异常处理程序中调用此函数。

作者建议:

  • 在引发错误的块的异常部分中调用此函数。 这样,即使异常在堆栈中进一步重新引发,您也可以拥有(并且可以记录)该关键行号。
  • 避免在堆栈中的中间程序中使用异常处理程序,并在堆栈中最外层程序的异常部分中调用该函数。
DBMS_UTILITY.FORMAT_CALL_STACK

此函数返回一个格式化字符串,显示 PL/SQL 应用程序内的执行调用堆栈。 它的用处不仅限于错误管理; 您还会发现它可以方便地跟踪代码的执行。 第 20 章将更详细地探讨该函数。

注:在 Oracle Database 12c 中,Oracle 引入了 UTL_CALL_STACK 包,该包还允许您访问调用堆栈、错误堆栈和回溯信息。 这个包将在第 20 章中进行探讨。

Combining Multiple Exceptions in a Single Handler

sql 复制代码
WHEN exception01 OR exception02

Unhandled Exceptions

如果引发的异常未处理,PL/SQL 将将其一直返回到运行 PL/SQL 的应用程序环境。 然后,该环境(例如 SQLPlus或 Java 程序等)会根据情况采取适当的操作; 对于 SQLPlus,会自动回退 DML 更改。

关于应用程序架构需要做出的一项关键决定是是否允许发生未处理的异常,建议:

  • 捕获任何异常。
  • 记录错误,以便开发人员分析问题。
  • 传回状态码、描述以及主机环境所需信息,以便主机环境采取适当的操作。

Propagation of Unhandled Exceptions

当异常产生时,首先在当前块寻找异常处理程序,如果没找到,则从当前块的外层寻找,以此类推,如果所有层都没有找到,则将异常返回到执行最外层PL/SQL的应用程序。

除非你用EXCEPTION_INIT关联了错误号,并且用RAISE_APPLICATION_ERROR关联了错误信息,否则所有用户定义的异常错误码为1,并且错误信息为"User Defined Exception"。

对于本地定义的异常,应包含异常处理程序。

Continuing Past Exceptions

当 PL/SQL 块中引发异常时,正常执行将停止,控制权将转移到异常部分。 一旦该块中引发异常,您就永远无法返回到执行部分。 然而,在某些情况下,继续过去的异常的能力正是所需的行为。

例如:

sql 复制代码
操作A;
操作B;

如果无论操作A是否成功,操作B都必须执行。则可以采用以下的方式:

sql 复制代码
BEGIN
   操作A;
   EXCEPTION
      WHEN OTHERS THEN log_error;
   END;

   BEGIN
      操作B;
   EXCEPTION
      WHEN OTHERS THEN log_error;
   END;

总结一下:如果存在匹配的处理程序,则可执行部分中引发的异常将始终在当前块中处理。 您可以在任何语句周围创建一个虚拟块,方法是在该语句前面添加 BEGIN,并在其后面添加 EXCEPTION 部分和 END 语句。 通过这种方式,您可以通过在代码中建立匿名块的缓冲区来控制异常引起的失败范围。

您还可以进一步采用此策略,将要隔离的代码移至单独的过程或函数中。 当然,这些命名的 PL/SQL 块也可能有自己的异常部分,并将提供相同的保护以防止完全失败。 使用过程和函数的一个关键优点是可以隐藏主线程序中的所有 BEGIN-EXCEPTION-END 语句。 这样,程序就更容易在多个上下文中阅读、理解、维护和重用。

还有其他方法可以继续处理 DML 异常。 您还可以将 SAVE EXCEPTIONS 与 FORALL 和 LOG ERRORS 结合使用,并与 DBMS_ERRORLOG 一起使用,以继续 DML 引发的异常。

Writing WHEN OTHERS Handling Code

您应该小心使用 WHEN OTHERS,因为它很容易"吞掉"错误并将其隐藏起来,不让外部块和用户看到。 具体来说,请注意 WHEN OTHER 处理程序,如果它们不重新引发当前异常或其他异常,那么应用程序的外部块将永远不会知道发生了错误。

PL/SQL可以对此发出警告:

sql 复制代码
SQL> ALTER SESSION SET plsql_warnings = 'enable:all';

Session altered.

CREATE OR REPLACE PROCEDURE plw6009_demo
AS
BEGIN
   DBMS_OUTPUT.put_line ('I am here!');
   RAISE NO_DATA_FOUND;
EXCEPTION
   WHEN OTHERS
   THEN
      NULL;
END plw6009_demo;
/

SP2-0804: Procedure created with compilation warnings

SQL> show errors
Errors for PROCEDURE PLW6009_DEMO:

LINE/COL ERROR
-------- -----------------------------------------------------------------
1/1      PLW-05018: unit PLW6009_DEMO omitted optional AUTHID clause;
         default value DEFINER used

7/9      PLW-06009: procedure "PLW6009_DEMO" OTHERS handler does not end
         in RAISE or RAISE_APPLICATION_ERROR

如果把NULL改为RAISE,则PLW-06009警告就没了。

PLSQL_WARNINGS可控制报告警告的级别,详见这里

Building an Effective Error Management Architecture

PL/SQL 错误引发和处理机制功能强大且灵活,但它们有一些缺点,以下是您将遇到的一些挑战:

  • 声明为 EXCEPTION 的变量只能引发和处理。 它最多有两个特征:错误代码和错误消息。 您不能将异常作为参数传递给程序,因此你必须剪切并粘贴处理程序代码; 重用异常处理代码非常困难。

  • 没有正式的方法来指定程序可能引发哪些异常。 而对于 Java,此信息成为程序规范的一部分。

  • Oracle 不提供任何方法来组织和分类特定于应用程序的异常。 它只是(大部分)保留了 -20,999 到 -20,000 之间的 1,000 个错误代码。

Decide on Your Error Management Strategy

在编写任何代码之前,在应用程序中建立一致的错误处理策略和体系结构非常重要。 为此,您必须回答如下问题:

  • 我如何以及何时记录错误以便可以查看和更正它们? 我应该将信息写入文件、数据库表和/或屏幕吗?
  • 如何以及何时向用户报告发生的错误? 用户应该看到并必须跟踪多少信息? 如何将晦涩难懂的数据库错误消息转换为用户可理解的文本?

更具体的问题,例如:

  • 我是否应该在每个 PL/SQL 块中包含异常处理部分?
  • 我应该只在顶层/最外层块中有一个异常处理部分吗?
  • 当发生错误时,事务该如何管理?

异常处理的复杂性部分在于这些问题都没有单一的正确答案。 它至少部分取决于应用程序架构及其使用方式(例如,批处理与用户驱动的事务)。 无论您如何回答,我强烈建议您将错误处理的策略和规则"编入"标准化包中。

Standardize Handling of Different Types of Exceptions

所有例外都是平等的吗? 并非如此。 例如,某些异常表明数据库存在非常严重的错误(例如 ORA-00600)。 其他异常,例如 NO_DATA_FOUND,经常发生,我们甚至不一定将它们视为错误,而更多地视为逻辑的条件分支("如果该行不存在,则执行此操作...")。 这些区别非常重要,PL/SQL 产品经理 Bryn Llewellyn 给出了一种非常有用的异常分类方法:

  • 刻意(Deliberate)

    代码架构本身依赖于其工作方式的异常。 您应该预测此异常并为其编写代码。 一个例子是 UTL_FILE.GET_LINE,当所有行都读完时(NO_DATA_FOUND),应关闭文件。

  • 不幸的(Unfortunate)

    这是一个预料之中的错误,甚至可能并不表明出现了问题。 一个示例是引发 NO_DATA_FOUND 的 SELECT INTO 语句。

  • 意外(Unexpected)

    这是一个"硬"错误,表明应用程序中存在问题。 一个示例是 SELECT INTO 语句,它应该返回给定主键的行,但却引发 TOO_MANY_ROWS。

对于SELECT INTO语句,返回NO_DATA_FOUND和TOO_MANY_ROWS异常都是有可能得。

How to benefit from this categorization

以下是作者建议的一些处理方式:

  • 商榷(Deliberate)

    您需要编写代码来预测这些异常。 避免将应用程序逻辑放在异常部分中。 异常部分应只包含处理错误所需的代码:记录错误数据、重新引发异常等。那里不应有特定于应用程序的逻辑,否则理解和维护会更困难 。

  • 不幸的

    如果不被视为错误,则不要将这些异常传播出去而不进行处理。 相反,返回一个值或状态标志来指示引发了异常。 然后,由程序的用户决定该程序是否应因错误而终止。 更好的是,和程序的调用者协商好是否引发异常,应该传递什么值来指示发生了异常。

  • 意外

    应记录所有意外错误,尽可能多的应用程序上下文,以帮助理解发生错误的原因。 然后,程序应因程序内引发的未处理异常(通常是同一异常)而终止,这可以使用 RAISE 语句来完成,强制调用程序停止并处理错误。

Organize Use of Application-Specific Error Codes

为了帮助管理您的错误代码并提供一个一致的接口供开发人员处理服务器错误,请考虑构建一个表来存储您使用的所有 -20,NNN 错误号及其关联的异常名称和错误消息。 然后,开发人员可以通过屏幕查看这些已经定义的错误,并选择适合他们情况的错误。

您可以采取的另一种方法是完全避免 −20,NNN 范围内的特定于应用程序的错误,而使用正数。Oracle 仅在整数范围的正数上使用 1 和 100。

Use Standardized Error Management Programs

健壮且一致的错误处理是正确构建的应用程序的绝对关键要素。 这种一致性对于用户和开发人员都非常重要。 如果在发生错误时提供易于理解、格式良好的信息,用户将能够更有效地向支持团队报告该错误,并且会更轻松地使用该应用程序。 如果应用程序始终以相同的方式处理和记录错误,那么支持和维护程序员将能够更轻松地修复和增强代码。

这里作者举了个例子,在异常处理部分,对于异常A,仅记录错误;对于异常A,记录错误,并重新RAISE此异常。记录异常的方式是插入到一张日志表中。

这种处理的问题在于,由于插入到日志表本身是事务,如果用户的事务回退,则记录日志的操作也会回退,也就是日志丢失了。

虽然可以避免此问题,例如写到文件,或autonomous transactions。但更建议的方式,是封装错误处理,这样可以保证更一致的错误处理,和代码重用。例如:

sql 复制代码
EXCEPTION
   WHEN NO_DATA_FOUND
   THEN
      errpkg.record_and_continue (...);

   WHEN OTHERS
   THEN
      errpkg.record_and_stop;
END;

现在,可以花更多的时间来构建应用程序中重要的元素,而不是担心繁琐的低级管理。

Work with Your Own Exception "Objects"

例外由一个标识符(名称)组成,您可以将其与号码和消息关联起来。 您可以引发异常,并且可以处理它。 仅仅如此。 而Java所有错误都源自单个 Exception 类, 您可以扩展该类,添加有关您想要跟踪的异常的其他特征(错误堆栈、上下文相关数据等),您还可以将其作为参数传递给方法。

虽然原生的异常有以上限制,但可以使用 Oracle 对象类型来实现类似Java的行为。例如:

sql 复制代码
CREATE TYPE exception_t AS OBJECT (
...
   )
   NOT FINAL;
/

CREATE TYPE dynsql_exception_t UNDER exception_t (
   sql_string CLOB )
   NOT FINAL;
/

...

类似于Java的Class。

Create Standard Templates for Common Error Handling

您无法将异常传递给程序,这使得在不同的 PL/SQL 块之间共享标准错误处理部分变得非常困难。

建议的方法还是封装错误处理,形成标准的模版。

Making the Most of PL/SQL Error Management

这一部分相当于总结了。

采用一致的、高质量的方法来处理错误,从而创建易于使用和调试的应用程序。

Oracle PL/SQL 的错误管理功能允许您以非常灵活的方式定义、引发和处理错误。 然而,其方法的局限性意味着您通常希望使用自己的特定于应用程序的代码和表来补充内置功能。

我建议您采取以下步骤来应对这一挑战:

  1. 研究并理解 PL/SQL 中的错误引发和处理是如何工作的。 这并不完全是直观的。 例如:声明部分中引发的异常将不会由该块的异常部分处理。

  2. 决定您将采用的全局错误管理方法。 何时何地处理错误? 需要保存哪些信息?如何保存? 异常如何传播到主机环境? 您将如何处理刻意的、不幸的和意外的错误?

  3. 建立一个标准框架供所有开发人员使用; 该框架将包括基础表、包,可能还有对象类型,以及使用这些元素的明确定义的过程。

  4. 创建团队中每个人都可以使用的模板,这样比自己编写错误处理代码更容易遵循标准。

终于在除夕春晚之前读完此章,可以安心看晚会了。

相关推荐
白云如幻42 分钟前
SQL99版链接查询语法
数据库·sql·mysql
爱吃烤鸡翅的酸菜鱼1 小时前
MySQL初学之旅(4)表的设计
数据库·sql·mysql·database
永乐春秋3 小时前
WEB-通用漏洞&SQL注入&CTF&二次&堆叠&DNS带外
数据库·sql
王小小鸭4 小时前
【开发小技巧11】用经典报表实现badge list效果,根据回显内容用颜色加以区分
oracle·oracle apex
大白要努力!4 小时前
android 使用SQLiteOpenHelper 如何优化数据库的性能
android·数据库·oracle
武子康6 小时前
大数据-230 离线数仓 - ODS层的构建 Hive处理 UDF 与 SerDe 处理 与 当前总结
java·大数据·数据仓库·hive·hadoop·sql·hdfs
Mephisto.java7 小时前
【大数据学习 | Spark】Spark的改变分区的算子
大数据·elasticsearch·oracle·spark·kafka·memcache
远歌已逝10 小时前
维护在线重做日志(二)
数据库·oracle
武子康15 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
爱上口袋的天空15 小时前
09 - Clickhouse的SQL操作
数据库·sql·clickhouse