知识库✳

教程与手册

全部教程
  1. 01Oracle 函数手册
  2. 02性能优化手册
  3. 03Linux Shell
  4. 04Oracle 学习与演练
  5. 05网络基础学习
  6. 06数据治理与管理
返回个人网站
甲Oracle 学习教程从 SQL 到 PL/SQL 与运维
首页课程目录搜索演练场
知识库

已完成 0 / 22 章

模块一 · 入门与准备

  • 01Oracle 数据库体系结构
  • 02环境安装与连接方式
  • 03SQL*Plus 与常用工具

模块二 · SQL 查询基础

  • 04SELECT 查询基础
  • 05过滤与排序
  • 06单行函数
  • 07多表连接
  • 08分组聚合与 HAVING
  • 09子查询

模块三 · 数据与对象

  • 10DML 与事务控制
  • 11表与数据类型
  • 12约束
  • 13索引与执行计划
  • 14视图、序列与同义词

模块四 · PL/SQL 编程

  • 15PL/SQL 块与变量
  • 16流程控制与游标
  • 17存储过程与函数
  • 18触发器
  • 19异常处理

模块五 · 运维与进阶

  • 20用户与权限管理
  • 21表空间与存储管理
  • 22备份与恢复
❖

Oracle 学习教程

返回个人网站知识库

示例代码基于 Oracle AI Database 26ai / 23ai,查询示例使用官方 HR 示例方案。

以 Next.js 16 · Tailwind CSS v4 · HeroUI v3 装帧

19
  1. 首页
  2. 课程目录
  3. 异常处理

第 19 章◆进阶◆建议 40 分钟◆模块四 · PL/SQL 编程

异常处理

识别预定义异常、定义自己的异常、正确使用 WHEN OTHERS,以及把错误信息传给调用方。

学完这一章,你应该能

  • 认识常见预定义异常及其触发条件
  • 会定义并抛出业务异常
  • 理解 WHEN OTHERS 的正确用法
  • 能用 RAISE_APPLICATION_ERROR 传递友好错误

19.1常见预定义异常

  • NO_DATA_FOUND:SELECT INTO 没查到数据,或引用集合中不存在的元素
  • TOO_MANY_ROWS:SELECT INTO 返回多行
  • VALUE_ERROR:类型转换或截断失败
  • INVALID_NUMBER:字符串转数字失败
  • DUP_VAL_ON_INDEX:唯一约束冲突
  • 注意:SQL%ROWCOUNT = 0 不会抛 NO_DATA_FOUND,UPDATE 没匹配到行不是异常
捕获并区分不同异常
DECLARE
  v_sal employees.salary%TYPE;
BEGIN
  SELECT salary INTO v_sal FROM employees WHERE department_id = 60;
  DBMS_OUTPUT.PUT_LINE(v_sal);
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    DBMS_OUTPUT.PUT_LINE('查询没有返回任何行');
  WHEN TOO_MANY_ROWS THEN
    DBMS_OUTPUT.PUT_LINE('查询返回了多行,SELECT INTO 只能接一行');
  WHEN ZERO_DIVIDE THEN
    DBMS_OUTPUT.PUT_LINE('除数不能为零');
  WHEN DUP_VAL_ON_INDEX THEN
    DBMS_OUTPUT.PUT_LINE('违反唯一约束');
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('未预期错误: ' || SQLCODE || ' - ' || SQLERRM);
END;
/

只作示例阅读:5 行写入限制下不执行任意 PL/SQL、循环写入或动态 SQL;只开放字面量 DBMS_OUTPUT 打印。请在独立学习数据库中练习。

19.2自定义异常

两种定义方式
DECLARE
  -- 方式一:自己声明,自己 RAISE
  e_salary_too_low EXCEPTION;

  -- 方式二:绑定到 Oracle 错误号
  e_child_found EXCEPTION;
  PRAGMA EXCEPTION_INIT(e_child_found, -2292);   -- 违反外键约束

  v_sal NUMBER;
BEGIN
  SELECT salary INTO v_sal FROM employees WHERE employee_id = 100;

  IF v_sal < 5000 THEN
    RAISE e_salary_too_low;
  END IF;

  DELETE FROM departments WHERE department_id = 60;   -- 有子记录会触发 -2292

EXCEPTION
  WHEN e_salary_too_low THEN
    DBMS_OUTPUT.PUT_LINE('工资低于下限,拒绝处理');
  WHEN e_child_found THEN
    DBMS_OUTPUT.PUT_LINE('该部门下还有员工,不能删除');
END;
/

只作示例阅读:5 行写入限制下不执行任意 PL/SQL、循环写入或动态 SQL;只开放字面量 DBMS_OUTPUT 打印。请在独立学习数据库中练习。

19.3把错误抛给调用方

RAISE_APPLICATION_ERROR
CREATE OR REPLACE PROCEDURE check_salary (p_sal IN NUMBER) IS
BEGIN
  IF p_sal IS NULL THEN
    RAISE_APPLICATION_ERROR(-20010, '工资不能为空');
  END IF;
  IF p_sal < 1000 THEN
    RAISE_APPLICATION_ERROR(-20011, '工资不能低于 1000');
  END IF;
END check_salary;
/

-- 调用方看到的就是自定义信息
BEGIN
  check_salary(500);
EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE(SQLCODE || ' -> ' || SQLERRM);
END;
/

错误号必须在 -20000 到 -20999 之间,这是 Oracle 留给应用自定义错误的区间。

只作示例阅读:5 行写入限制下只开放单独创建普通空表;CTAS、删表、清空表、改结构、触发器和存储程序不允许执行。请在独立学习数据库中练习。

19.4WHEN OTHERS 的正确姿势

  • WHEN OTHERS 必须放在最后,它是兜底分支
  • 不要在 WHEN OTHERS 里什么都不做——这会把错误吞掉,是排查噩梦的根源
  • 至少要记录 SQLCODE 和 SQLERRM,或调用 DBMS_UTILITY.FORMAT_ERROR_BACKTRACE 拿到出错行号
  • 如果不打算处理,就 RAISE; 原样抛出,让上层决定
  • 异常会被传播到调用者;过程中未处理的异常会回滚该过程内的修改
记录完整错误堆栈
BEGIN
  -- 可能出错的逻辑
  UPDATE employees SET salary = salary / 0 WHERE employee_id = 100;
EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('错误码 : ' || SQLCODE);
    DBMS_OUTPUT.PUT_LINE('错误信息: ' || SQLERRM);
    DBMS_OUTPUT.PUT_LINE('回溯   : ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
    RAISE;
    -- 记录完再抛出去,不吞错误
END;
/

只作示例阅读:5 行写入限制下不执行任意 PL/SQL、循环写入或动态 SQL;只开放字面量 DBMS_OUTPUT 打印。请在独立学习数据库中练习。

练习

3 题 · 先自己写,再展开对照

参考答案

SELECT INTO 返回 0 行 → NO_DATA_FOUND
SELECT INTO 返回多行 → TOO_MANY_ROWS

UPDATE 影响 0 行 → 不抛异常,用 SQL%ROWCOUNT 判断即可。

这三个经常被混在一起。记住规律:异常是针对「结果集形状不符合预期」,
而「影响 0 行」是合法结果。

只作示例阅读:查询不能包含写入、行锁或内联程序。请在独立学习数据库中练习。

自评要点

  • NO_DATA_FOUND / TOO_MANY_ROWS 对应关系
  • DML 0 行不是异常

参考答案

因为它把错误彻底吞掉了:程序继续往下跑,数据可能已经处于
不一致状态,而日志里什么都没有,事后完全无法排查。

正确做法是记录足够信息后再决定:

EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('错误码 : ' || SQLCODE);
    DBMS_OUTPUT.PUT_LINE('错误信息: ' || SQLERRM);
    DBMS_OUTPUT.PUT_LINE('回溯   : ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
    RAISE;
    -- 记录完再抛出去,交给上层决定

关键点:FORMAT_ERROR_BACKTRACE 能给出出错的具体行号,
比只有 SQLERRM 有用得多;RAISE; 表示原样重新抛出,不吞错误。

自评要点

  • 指出吞错误会导致数据不一致且无法排查
  • 记录 SQLCODE / SQLERRM / FORMAT_ERROR_BACKTRACE
  • 用 RAISE; 重新抛出

参考答案

范围是 -20000 到 -20999。

这个区间是 Oracle 专门留给应用自定义错误的,不会与 Oracle 自身的
错误号冲突(ORA-20000 到 ORA-20999 保留给用户)。

用法:
RAISE_APPLICATION_ERROR(-20010, '工资不能为空');

调用方捕获到的 SQLCODE 就是 -20010,SQLERRM 是自定义消息,
这样能把业务错误清晰地传递到应用层。

自评要点

  • -20000 到 -20999
  • 知道它能把业务错误传到调用方
← 上一章

触发器

下一章 →

用户与权限管理