PLSQL-ERROR管理

6 篇文章 0 订阅
5 篇文章 0 订阅

一、异常概述

       在PL/SQL中有三类异常:内部定义异常、预定义异常和用户定义异常。

1、内部定义异常(An internally defined exception)

       内部定义异常是由Oracle数据库进程内部引发的,这种异常总是有一个错误代码,但没有名称,除非它是由PL/SQL或自己的代码指定的。内部定义的异常的一个示例:ORA-00060(等待资源时检测到死锁)。

2、预定义异常(predefined exception)

       预定义异常是由PL/SQL分配名称的内部定义的异常。大多数预定义的异常都是在标准包(Oracle数据库提供的包,定义了PL/SQL语言的许多通用编程元素)中定义的,并且是最常见的异常。一个例子:ORA-00001,它在PL/SQL中被赋值为DUP_VAL_ON_INDEX,并在违反唯一索引约束时引发。

3、用户定义异常(user-defined exception )

       用户定义异常是在程序单元的声明部分中声明的。用户定义的异常可以与内部定义的异常相关联(也就是说,可以为未命名的异常提供名称),也可以与特定于应用程序的错误相关联。

      每个异常都有一个错误代码和与之关联的错误信息。 Oracle数据库提供了在处理异常时检索这些值的函数。(参见下表)

描述如何获取
错误代码。当需要查找可能导致此类问题的通用信息时,此代码非常有用。SQLCODE
注意: 不能在SQL语句中调用此函数
错误信息. 该文本通常包含特定于应用程序的数据,例如约束的名称或与问题关联的列。SQLERRM or DBMS_UTILITY.FORMAT_ERROR_STACK
注意: 不能在SQL语句中调用SQLERRM。
错误发生的行数。这个功能是在Oracle数据库10g版本2中添加的,在跟踪错误原因方面非常有用。DBMS_UTILITY.FORMAT_ERROR_BACKTRACE
执行调用堆栈。这就回答了问题“我是怎么来到这里的?”并向您显示代码在DBMS_UTILITY.FORMAT_CALL_STACK被调用的路径。DBMS_UTILITY.FORMAT_CALL_STACK

二、异常抛出

       在大多数情况下,当应用程序中出现异常时,Oracle数据库将执行此操作。也就是说,在您的代码执行过程中出现了某种问题,您无法控制这个过程。一旦引发异常,您所能做的就是处理异常——或者让它“转移”,不被处理到主机环境中。

       然而,您也可以在自己的代码中引发异常。你为什么要这么做?因为并不是应用程序中的所有错误都是由于Oracle数据库实例内部处理失败造成的。还有一种可能是,某个数据条件构成了应用程序中的错误,在这种情况下,您需要停止对算法的处理,并且很可能会通知用户发生了错误。

PL / SQL提供了两种引发异常的机制:

  • RAISE

  • RAISE_APPLICATION_ERROR(内置存储过程)

1、RAISE

       可以使用RAISE语句引发用户定义的异常或Oracle数据库预定义的异常。在下面的示例中,如果用户为department ID提供了空值,将引发VALUE_ERROR异常:

CREATE OR REPLACE PROCEDURE 
process_department (
   department_id_in IN INTEGER)
IS
BEGIN
   IF department_id_in IS NULL
   THEN
      RAISE VALUE_ERROR;
   END IF;

2、RAISE_APPLICATION_ERROR

       RAISE语句引发一个异常,阻止当前块继续。还设置当前错误代码和错误信息。这个错误信息—例如“ORA-06502: PL/SQL:数值或值错误”—由Oracle数据库提供,通常是通用的。

       这种错误消息可能足以报告数据库错误,但是如果某个特定于应用程序的错误——例如“员工太年轻”或“工资不能超过1000美元”——有异常怎么办?“数值或数值错误”信息不会帮助用户理解他们做错了什么以及如何修复它。

       如果在发生错误时需要将特定于应用程序的消息传递回用户,则应该调用RAISE_APPLICATION_ERROR内置过程。此过程接受一个整数(您的错误代码),其值必须介于-20,999和-20,000之间,以及一个字符串(您的错误消息)。

       下面是一个使用RAISE_APPLICATION_ERROR的示例:雇员必须至少满18岁。如果出生日期不符合,则抛出一个错误,以便停止插入或更新,并向用户返回一条信息。

CREATE OR REPLACE PROCEDURE 
validate_employee (
   birthdate_in IN DATE)
IS
BEGIN
   IF birthdate_in > 
      ADD_MONTHS (SYSDATE, -12 * 18)
   THEN
      RAISE_APPLICATION_ERROR (-20500
        , '雇员必须至少满18岁。'); 
   END IF;
END;

三、自定义异常

       可能有两个原因想要定义自己的异常(使用用户定义的异常):给一个没有被Oracle数据库分配名称的错误命名,或者定义一个特定于应用程序的异常,比如“金额值太小”错误,错误代码-20000。

CREATE OR REPLACE PROCEDURE 
process_balance (
   balance_in IN NUMBER)
IS
   e_balance_too_low   EXCEPTION;
   PRAGMA EXCEPTION_INIT (
      e_balance_too_low, -20000);
BEGIN
   IF balance_in < 1000
   THEN
      RAISE e_balance_too_low;
   END IF;
END;

四、异常处理

       Oracle数据库可能会引发内部或预定义的异常,您还可以显式地引发为应用程序定义的异常。接下来,您需要决定您的程序如何处理或处理这个异常。
       如果您不希望异常在处理之前离开您的块或子程序,那么您必须包含一个异常部分来捕获异常。异常部分以关键字exception开头,然后包含一个或多个WHEN子句。WHEN子句可以指定单个异常(通过名称),多个异常用OR连接或则任何异常。

  1. 捕获 NO_DATA_FOUND 异常, 通常在执行SELECT-INTO语句并没有找到行时引发。

       WHEN NO_DATA_FOUND 
       THEN 
  2. 捕获 NO_DATA_FOUND 或 DUP_VAL_ON_INDEX 预定义异常。

       WHEN NO_DATA_FOUND OR
            DUP_VAL_ON_INDEX
       THEN 
  3. 捕获任何异常
       WHEN OTHERS 
       THEN

异常部分可以有多个WHEN子句,但是如果你有一个WHEN OTHERS子句,它必须在最后。

       定义一个或多个WHEN子句很容易。异常部分的棘手部分是在捕获异常后决定要做什么。通常,异常处理程序中的代码应执行以下两个步骤:

       1、在某种日志中记录错误信息,通常是数据库表。

        2、抛出相同或不同的异常,因此将未处理的异常传播到外部块。

重新引用异常

       您可以简单地记录关于错误的信息,然后不重新检查异常。这种方法的问题是您的应用程序已经“吞噬”了一个错误。用户(或正在运行的脚本)将不知道存在问题。在某些情况下,这可能是可以的,但是非常罕见。在几乎所有发生错误的情况下,您却是需要确保运行引发错误的代码的人员或作业得到通知。

       Oracle数据库使得使用RAISE语句很容易做到这一点。如果在可执行部分中使用RAISE,则必须指定要引发的异常。

       RAISE NO_DATA_FOUND;

       但是在异常处理程序中,您也可以使用RAISE而不使用任何异常。

       RAISE;

错误记录

       通过将存储过程声明为一个自治事务,我可以提交或回滚对这个过程中的表所做的任何更改,而不会影响在会话中所做的其他更改。因此,现在我可以将新行保存到错误日志中,以后的业务事务回滚不会消除这些信息。

       异常记录存储过程作为自治事务提交

CREATE OR REPLACE PROCEDURE record_error
IS
   PRAGMA AUTONOMOUS_TRANSACTION;
   l_code   PLS_INTEGER := SQLCODE;
   l_mesg VARCHAR2(32767) := SQLERRM; 
BEGIN
   INSERT INTO error_log (error_code
                        ,  error_message
                        ,  backtrace
                        ,  callstack
                        ,  created_on
                        ,  created_by)
        VALUES (l_code
              ,  l_mesg 
              ,  sys.DBMS_UTILITY.format_error_backtrace
              ,  sys.DBMS_UTILITY.format_call_stack
              ,  SYSDATE
              ,  USER);
   COMMIT;
END;

通过定义的这个日志记录存储过程,可以非常轻松快速地编写异常处理程序,如下所示:

EXCEPTION
   WHEN OTHERS
   THEN
      record_error();
      RAISE;

声明时异常

       如果在块的声明部分中引发异常,则异常将传播到外部块。换句话说,块的异常部分只能捕获块的可执行部分中引发的异常。

       以下块包含WHEN OTHERS处理程序,它应该捕获块中引发的任何异常并只显示错误代码:

DECLARE
  l_number   NUMBER (1) := 100;
BEGIN
  statement1;
  ...
  statementN;
EXCEPTION
  WHEN OTHERS
  THEN
    DBMS_OUTPUT.put_line (SQLCODE);
END;

       当执行块时,Oracle数据库将尝试将值100赋给l_number。但是,因为它被声明为NUMBER(1),所以100不会“适合”这个变量。因此,Oracle数据库将导致ORA-06502错误,这是PL/SQL中预定义的VALUE_ERROR错误。

       因为异常是在声明变量的过程中引发的,所以异常处理程序不会捕捉到这个错误。相反,我将看到一个未处理的异常:

ORA-06502: PL/SQL: numeric or value error: number precision too large
ORA-06512: at line 2 

       因此,应该避免在声明部分中为变量赋值,除非您确定不会引发错误。相反,您可以在可执行部分中赋值,然后异常处理程序可以捕获并记录错误:

DECLARE
  l_number   NUMBER (1);
BEGIN
  l_number := 100;

  statement1;
  ...
  statementN;
EXCEPTION
  WHEN OTHERS
  THEN
    DBMS_OUTPUT.put_line (SQLCODE);
END;

五、异常和回滚

       未处理的异常不会自动导致会话中未处理的更改回滚。实际上,除非将ROLLBACK语句显式地编码到异常部分或异常传播未处理到主机环境,否则不会发生回滚。让我们看一个例子。

假设我编写了一个执行两种数据操作语言(DML)操作的代码块:

1.从Employees表中删除部门20中的所有员工。

2.通过将当前薪水乘以200来向所有剩余员工加薪。

        但是工资列上的约束被定义为数字(8,2)。一些员工的工资已经非常高,新的工资金额将会违反这个约束,导致Oracle数据库提高了“ORA-01438:值大于该列允许的指定精度”错误。

假设我在SQL * Plus会话中运行以下块:

BEGIN
   DELETE FROM employees
         WHERE department_id = 20;
   UPDATE employees
      SET salary = salary * 200;
EXCEPTION
   WHEN OTHERS
   THEN
      DECLARE
         l_count   PLS_INTEGER;
      BEGIN
         SELECT COUNT (*)
           INTO l_count
           FROM employees
          WHERE department_id = 20;

         DBMS_OUTPUT.put_line (l_count);
         RAISE;
      END;
END;

       删除操作成功完成,但是Oracle数据库在尝试执行UPDATE语句时引发了ORA-01438错误。我捕获错误并显示Employees表中department_id = 20的行数。会显示“0”,因为UPDATE语句失败不会导致会话回滚。

       然而,在我显示计数之后,我重新抛出相同的异常。因为没有封闭块,并且此最外层块以未处理的异常终止,所以此块中所做的任何更改都将由数据库回滚。

       因此,在运行此块后,部门20中的员工仍将在表中。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

为何生存

知识共享,共同进步!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值