知识库✳

教程与手册

全部教程
  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 装帧

18
  1. 首页
  2. 课程目录
  3. 触发器

第 18 章◆高级◆建议 45 分钟◆模块四 · PL/SQL 编程

触发器

在数据变动时自动执行逻辑:审计日志、自动维护字段、复杂业务规则的守门人。

学完这一章,你应该能

  • 区分行级与语句级触发器
  • 会用 :NEW / :OLD 访问变更前后的值
  • 知道变异表问题及规避方式
  • 理解触发器带来的隐性成本

18.1行级与语句级

  • FOR EACH ROW 决定是行级触发器;不写就是语句级
  • 行级里可用 :NEW 和 :OLD;INSERT 时 :OLD 无意义,DELETE 时 :NEW 无意义
  • BEFORE 可以修改 :NEW 的值;AFTER 不能
  • WHEN 子句可以加执行条件,减少无谓触发
自动维护审计字段
CREATE OR REPLACE TRIGGER trg_emp_biu
BEFORE INSERT OR UPDATE ON employees_demo
FOR EACH ROW                      -- 行级:每影响一行执行一次
BEGIN
  IF INSERTING THEN
    :NEW.created_by := USER;
    :NEW.created_at := SYSDATE;
  ELSIF UPDATING THEN
    :NEW.updated_by := USER;
    :NEW.updated_at := SYSDATE;
  END IF;
END;
/

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

语句级:一次 DML 只执行一次
CREATE OR REPLACE TRIGGER trg_emp_audit_stmt
AFTER INSERT OR UPDATE OR DELETE ON employees_demo
BEGIN
  INSERT INTO audit_log (log_time, db_user, action, note)
  VALUES (SYSDATE, USER, 'DML', 'employees_demo 发生数据变更');
END;
/

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

18.2变异表问题

行级触发器里不允许查询或修改「正在被这条 DML 改动的那张表」,会报 ORA-04091。这是 Oracle 的保护机制,避免读到自己正在改的中间状态。

绕过变异表的常见做法
-- ✗ 行级触发器里查同一张表 → ORA-04091
-- CREATE TRIGGER ... FOR EACH ROW
-- BEGIN
--   SELECT COUNT(*) INTO v FROM employees_demo WHERE ...;
-- 报错
-- END;

-- ✓ 方案一:用复合触发器(11g+),在 AFTER STATEMENT 阶段汇总
CREATE OR REPLACE TRIGGER trg_emp_compound
FOR INSERT OR UPDATE ON employees_demo
COMPOUND TRIGGER
  TYPE t_ids IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
  g_ids t_ids;
  g_cnt PLS_INTEGER := 0;

  AFTER EACH ROW IS
  BEGIN
    g_cnt := g_cnt + 1;
    g_ids(g_cnt) := :NEW.employee_id;
  END AFTER EACH ROW;

  AFTER STATEMENT IS
  BEGIN
    -- 此时可以安全查询 employees_demo
    INSERT INTO audit_log (log_time, db_user, action, note)
    VALUES (SYSDATE, USER, 'BATCH',
            '本次共影响 ' || g_cnt || ' 行');
  END AFTER STATEMENT;
END;
/

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

提示 方案二是把逻辑挪到存储过程里显式调用。触发器适合做轻量的、与业务无关的横切逻辑;复杂业务规则放在触发器里会让排查变得非常困难。

18.3触发器的成本与注意事项

  • 触发器对应用是「隐式」的,容易造成排查困难,新人接手时常常找不到逻辑在哪
  • 行级触发器在大批量 DML 时开销明显(每行一次 PL/SQL 调用)
  • 触发器里的异常会导致整个 DML 失败
  • 触发器里的自治事务(PRAGMA AUTONOMOUS_TRANSACTION)可用于记录日志而不影响主事务
  • 用 ALTER TRIGGER ... DISABLE 可以在批量导入时临时关掉
  • 查询触发器定义:SELECT trigger_name, status FROM user_triggers;

练习

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

参考答案

行级触发器(FOR EACH ROW):DML 每影响一行就执行一次,
可以访问 :NEW(新值)和 :OLD(旧值),也能在 BEFORE 里修改 :NEW。

语句级触发器:整个 DML 语句只执行一次,不管影响多少行。
适合做「本次操作共影响了多少行」这类汇总动作。

:NEW / :OLD 的可用性:
- INSERT:只有 :NEW 有意义
- DELETE:只有 :OLD 有意义
- UPDATE:两者都有

行级触发器在大批量 DML 时开销明显(每行一次 PL/SQL 调用),要留意。

自评要点

  • FOR EACH ROW 决定行级
  • 说清 :NEW/:OLD 在各 DML 下的可用性

参考答案

变异表(mutating table)指的是:行级触发器里试图查询或修改
正在被这条 DML 改动的那张表。

它会报 ORA-04091: table XXX is mutating, trigger/function may not see it

Oracle 这样设计是为了避免读到自我修改过程中的中间状态。

规避方式:
1. 复合触发器(COMPOUND TRIGGER,11g+):在 AFTER EACH ROW 里只把
   关键值攒到内存集合,在 AFTER STATEMENT 阶段再查表做汇总。
2. 把逻辑挪到存储过程里,由应用显式调用。
3. 用语句级触发器(不需要行数据时)。

更重要的判断:触发器适合轻量的横切逻辑(审计字段、时间戳)。
复杂业务规则塞进触发器会让排查变得非常困难——新人接手时
常常找不到逻辑写在哪。

自评要点

  • ORA-04091
  • 说出复合触发器的两阶段做法
  • 对触发器的适用边界有判断

参考答案

可以临时禁用触发器,导入完再启用:

ALTER TRIGGER trg_name DISABLE;
-- 导入 ...
ALTER TRIGGER trg_name ENABLE;

注意两个前提:
1. 确认这个触发器在导入期间不做必要的事情(比如审计字段填充),
   否则导入的数据会缺这些字段,需要事后补。
2. 启用时若触发器语义要求校验存量数据,ENABLE 可能会失败,
   必要时用 ENABLE NOVALIDATE。

查询触发器状态:SELECT trigger_name, status FROM user_triggers;

自评要点

  • ALTER TRIGGER ... DISABLE / ENABLE
  • 指出禁用会导致该触发器原本的副作用缺失,需要补数据
← 上一章

存储过程与函数

下一章 →

异常处理