Skip to content

适用虚谷数据库版本

v12.9



适用虚谷数据库版本

v12.9


异常处理

📄字数 4.7K
👁️阅读量 加载中...

在 PL/SQL 中,异常处理Exception Handling是用于捕获和处理程序执行过程中发生的错误的机制。合理的异常处理可以提高程序的健壮性和可维护性。异常包括预定义异常,非预定义异常(错误码)、自定义异常、以及异常处理函数等。

  • 异常处理优点如下:

    • 健壮性:使程序能够处理错误,避免程序突然崩溃,并向用户或调用者提供明确的错误信息。
    • 可控性:允许程序员在错误发生时应该采取行动(如记录错误、回滚事务、尝试替代方案、通知用户等)。
    • 事务完整性:通常在异常处理块中进行事务回滚 (ROLLBACK),确保数据库状态的一致性,避免部分更新。
    • 代码可读性与可维护性:将错误处理逻辑集中到专门的代码块 (EXCEPTION块) 中,与主要业务逻辑 (BEGIN 块) 分离。
  • 异常处理的基本流程如下:
    程序在BEGIN块中正常执行,如果发生错误(异常被引发或抛出RAISED),程序会立即跳转到当前块的EXCEPTION部分, 在EXCEPTION部分,查找与该异常类型匹配的WHEN子句。如果找到匹配的WHEN子句,则执行该子句下的处理代码,处理完成后,程序继续执行EXCEPTION块之后的语句;如果没有找到匹配的WHEN子句,则异常会传播到外层调用块执行,或者最终由数据库自身处理。

异常处理结构

PL/SQL的异常处理结构基于BEGIN ... EXCEPTION ... END;

SQL
DECLARE
    -- 声明部分 (变量、常量、游标、类型等)
    -- 异常定义
BEGIN
    -- 可执行部分 (主要业务逻辑代码)
    -- 可能引发异常的语句在这里执行
EXCEPTION
    -- 异常处理部分
    WHEN exception_name1 THEN
        -- 处理 exception_name1 异常的代码
        statement1;
        statement2;
        ...
    WHEN exception_name2 THEN
        -- 处理 exception_name2 异常的代码
        ...
    WHEN OTHERS THEN
        -- 处理所有未被前面WHEN子句捕获的异常的代码
        ...
END;
/
  • 简要解释

    • DECLARE (可选): 声明局部变量、常量、游标、类型等。
    • BEGIN: 包含程序的主要执行逻辑,异常可能发生的地方。
    • EXCEPTION: 关键字,异常处理部分。
      - WHEN exception_name THEN: 指定要捕获的异常类型。
      - exception_name 可以是:XuguDB预定义的异常名称(如:ZERO_DIVIDE(除数为0)、NO_DATA_FOUND(查询无结果)、TOO_MANY_ROWS(查询结果太多)、DUP_VAL_ON_INDEX(违反唯一值约束))。也可以是:用户自定义的异常名称(需要在 DECLARE 部分声明),通过语法ExceptionDef实现。
      - WHEN OTHERS THEN:用于捕获所有未被前面特定WHEN子句处理的异常,建议在关键代码块(尤其是涉及数据库修改的块)中总是包含 WHEN OTHERS,以确保没有未处理的异常导致不可预测的行为或数据不一致。
      - 异常处理代码: 当匹配到 WHEN 子句时执行的语句块,通常包括记录日志、回滚事务 (ROLLBACK)、设置错误标志、通知用户等操作。
    • END: 结束整个 PL/SQL 块。
  • 异常处理语句共包括三个部分:

    • 异常定义
    • 抛出异常
    • 异常处理

语法定义

  • 异常定义
  • 参数说明

    • ExceptionDef:用户自定义异常语句,使用关键字 EXCEPTION 在 DECLARE 中定义。
    • exception_name:用户自定义异常名称。
  • 抛出异常

  • 参数说明

    • ThrowStmt:抛出异常语句,抛出异常语句在块语句体或过程体中使用,可使用关键字 RAISE 或 THROW。
    • ColumnName:异常名称。
    • b_expr:附带错误信息。
  • 异常处理

(pl_stmt_list::=)

  • 参数说明:
    • 可以存在多个 WHEN 子句,每个子句处理一种特定的异常,使用WHEN OTHERS THEN可以捕获所有未被捕获的异常。
    • ColumnName:异常名称,如 TOO_MANY_ROWS、ZERO_DIVIDE 等。
    • ICONST:错误号。

异常类型

异常类型主要包括预定义异常、自定义异常、非预定义异常(错误码)、以及异常处理函数。

预定义异常

XuguDB 在 PL/SQL 内置了一系列常见的运行时错误对应的异常,具有标准的名称(如:NO_DATA_FOUND)和关联的错误代码(如:19009)。这些异常是隐式声明的,无需在 DECLARE 部分声明,可直接在 EXCEPTION 块的 WHEN 子句中使用。

预定义异常如下表所示:

数据库异常标识异常信息错误码
XuguDBZERO_DIVIDE除数为019005
XuguDBNO_DATA_FOUND查询无结果19009
XuguDBTOO_MANY_ROWS查询结果太多19010
XuguDBDUP_VAL_ON_INDEX违反唯一值约束13001
  • 示例:
sql
SQL> DECLARE
         x INT := 2;
         y INT := 0;
     BEGIN
         x := x / y;   
        EXCEPTION 
            WHEN ZERO_DIVIDE THEN 
                 SEND_MSG('除数为0');
     END;
     /
-- 输出
除数为0 

SQL> DECLARE
         x INT := 2;
         y INT := 0;
     BEGIN
         x := x / y;   
        EXCEPTION 
            WHEN 19005 THEN 
                 SEND_MSG('除数为0');
     END;
     /

-- 输出
除数为0

自定义异常

自定义异常必须在DECLARE部分使用EXCEPTION关键字声明,语法参考上文ExceptionDef

  • 示例:
sql
SQL> CREATE OR REPLACE PROCEDURE pro_1(a INT) IS 
     DECLARE
        e1 EXCEPTION;
        e2 EXCEPTION;
     BEGIN
        IF a > 5 THEN
            THROW EXCEPTION e1 ;
        ELSE
            THROW e2;
        END IF;
     EXCEPTION 
        WHEN e1 THEN
            send_msg('抛出异常1');
        WHEN e2 THEN 
            send_msg('抛出异常2');
        WHEN OTHERS THEN 
            send_msg('其它抛出异常');
     END; 
     /

SQL> EXECUTE pro_1(2);  
     抛出异常2

非预定义异常

XuguDB 通过PRAGMA EXCEPTION_INIT将自定义异常名称与一个特定的错误号关联起来,当在代码中使用RAISE_APPLICATION_ERROR函数抛出该错误代码时,可以在外层EXCEPTION块中通过自定义异常名称来捕获它,使代码更清晰。通过关联的错误号捕获异常信息,对于未命名的内部异常则可使用该方式为其添加异常名称,并为其编写一个特定的处理程序,而不是使用OTHERS处理程序。

  • 语法定义
  • 参数说明

    • name:当前PL/SQL语句、子程序或包中声明的用户自定义异常名。
    • integer:错误码。
  • 示例: 自定义异常名与数据库定义错误号E16005关联

sql
SQL> CREATE TABLE tb_ex(id INT PRIMARY KEY);

SQL> DECLARE
        no_null EXCEPTION;
        PRAGMA EXCEPTION_INIT(no_null,16005);
     BEGIN
        INSERT INTO tb_ex VALUES(NULL);
     EXCEPTION
        WHEN no_null THEN
            DBMS_OUTPUT.PUT_LINE(SQLCODE);
            DBMS_OUTPUT.PUT_LINE(SQLERRM);  
     END;
      /

-- 输出:
16005
字段 ID 不能取空值
  • 示例: RAISE_APPLICATION_ERROR 函数自定义错误码
sql
SQL> DECLARE
        a NUMERIC(4,0) := NULL;
     BEGIN
        IF a IS NULL THEN
            RAISE_APPLICATION_ERROR(9999, 'A为空');
        END IF;
     END;
      /

-- 输出
Error: [E9999 L5 C1] A为空

异常处理函数

XuguDB 支持的异常处理函数如下表所示:

函数详细信息
SQLCODE详细参考SQLCODE
SQLERRM详细参考SQLERRM
RAISE_APPLICATION_ERROR详细参考RAISE_APPLICATION_ERROR
DBMS_UTILITY.FORMAT_ERROR_STACK详细参考FORMAT_ERROR_STACK
DBMS_UTILITY.FORMAT_ERROR_BACKTRACE详细参考FORMAT_ERROR_BACKTRACE

引发异常

RAISE语句用于显式引发一个已声明的异常(通常是用户自定义异常)。

  • 示例:
sql
SQL> DECLARE
         sal_too_high EXCEPTION;
         cur_sal NUMBER := 8000;
         max_sal NUMBER := 6000;
         err_oneous_sal NUMBER;
     BEGIN
         BEGIN
             IF cur_sal > max_sal THEN
                 RAISE sal_too_high; 
             END IF;
         EXCEPTION
             WHEN sal_too_high THEN -- 开始处理异常
                 err_oneous_sal := cur_sal;
                 DBMS_OUTPUT.PUT_LINE ('Salary:' || err_oneous_sal ||' is out of range.');
                 DBMS_OUTPUT.PUT_LINE  ('Maximum salary is ' || max_sal || '.');
             RAISE; 
        END;
     EXCEPTION
         WHEN sal_too_high THEN -- 最后处理异常
             cur_sal := max_sal;
             DBMS_OUTPUT.PUT_LINE ('Revising salary from ' || err_oneous_sal ||' to ' || cur_sal || '.');
     END;
     /

-- 输出
Salary:8000 is out of range.
Maximum salary is 6000.
Revising salary from 8000 to 6000.