Oracle 触发器应用

Oracle 触发器应用

适用版本:Oracle Database 10g / 11g / 12c / 19c / 23ai 文档版本:v1.0 / 2026-07


1. 概述

触发器自动响应 DML/DDL 事件[1]:

类型

  • DML 触发器
  • DDL 触发器
  • INSTEAD OF(视图)
  • SYSTEM 事件

详细见:Oracle PL/SQL 触发器


2. DML 触发器

2.1 BEFORE INSERT

CREATE OR REPLACE TRIGGER trg_emp_audit
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
  :new.created_at := SYSTIMESTAMP;
  :new.created_by := USER;
END;
/

2.2 AFTER INSERT/UPDATE/DELETE

CREATE OR REPLACE TRIGGER trg_emp_history
AFTER INSERT OR UPDATE OR DELETE ON employees
FOR EACH ROW
BEGIN
  IF INSERTING THEN
    INSERT INTO emp_history (id, action, action_time)
    VALUES (:new.id, 'INSERT', SYSTIMESTAMP);
  ELSIF UPDATING THEN
    INSERT INTO emp_history (id, action, action_time)
    VALUES (:new.id, 'UPDATE', SYSTIMESTAMP);
  ELSIF DELETING THEN
    INSERT INTO emp_history (id, action, action_time)
    VALUES (:old.id, 'DELETE', SYSTIMESTAMP);
  END IF;
END;
/

2.3 复合触发器(11g+)

CREATE OR REPLACE TRIGGER trg_emp_compound
FOR INSERT OR UPDATE ON employees
COMPOUND TRIGGER
  -- 声明(仅一次)
  v_count NUMBER := 0;
  
  BEFORE STATEMENT IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE('Before statement');
  END BEFORE STATEMENT;
  
  BEFORE EACH ROW IS
  BEGIN
    :new.updated_at := SYSTIMESTAMP;
  END BEFORE EACH ROW;
  
  AFTER EACH ROW IS
  BEGIN
    v_count := v_count + 1;
  END AFTER EACH ROW;
  
  AFTER STATEMENT IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE('Rows: ' || v_count);
  END AFTER STATEMENT;
END;
/

3. INSTEAD OF 触发器

3.1 视图触发

CREATE OR REPLACE VIEW v_emp_dept AS
SELECT e.id, e.name, d.dept_name, e.dept_id
FROM employees e, departments d
WHERE e.dept_id = d.id;

CREATE OR REPLACE TRIGGER trg_v_emp_dept
INSTEAD OF INSERT ON v_emp_dept
FOR EACH ROW
BEGIN
  INSERT INTO employees (id, name, dept_id) VALUES (:new.id, :new.name, :new.dept_id);
END;
/

-- 插入视图
INSERT INTO v_emp_dept (id, name, dept_name, dept_id) 
VALUES (1, 'Alice', 'IT', 10);

4. DDL 触发器

4.1 数据库级

CREATE OR REPLACE TRIGGER trg_ddl_log
AFTER CREATE OR ALTER OR DROP ON DATABASE
BEGIN
  INSERT INTO ddl_log (event, object_owner, object_name, object_type, event_time, username)
  VALUES (
    ORA_SYSEVENT, ORA_DICT_OBJ_OWNER, ORA_DICT_OBJ_NAME, 
    ORA_DICT_OBJ_TYPE, SYSTIMESTAMP, ORA_LOGIN_USER
  );
END;
/

4.2 SCHEMA 级

CREATE OR REPLACE TRIGGER trg_no_drop
BEFORE DROP ON SCOTT.SCHEMA
BEGIN
  IF ORA_DICT_OBJ_NAME LIKE 'EMP%' THEN
    RAISE_APPLICATION_ERROR(-20001, 'Cannot drop EMP tables');
  END IF;
END;
/

5. SYSTEM 事件

5.1 LOGON/LOGOFF

CREATE OR REPLACE TRIGGER trg_logon
AFTER LOGON ON DATABASE
BEGIN
  INSERT INTO logon_log (username, logon_time)
  VALUES (USER, SYSTIMESTAMP);
END;
/

CREATE OR REPLACE TRIGGER trg_logoff
BEFORE LOGOFF ON DATABASE
BEGIN
  UPDATE logon_log 
  SET logoff_time = SYSTIMESTAMP
  WHERE username = USER AND logoff_time IS NULL;
END;
/

5.2 STARTUP/SHUTDOWN

CREATE OR REPLACE TRIGGER trg_startup
AFTER STARTUP ON DATABASE
BEGIN
  INSERT INTO startup_log (event_time, event) 
  VALUES (SYSTIMESTAMP, 'STARTUP');
END;
/

6. FOLLOWS

6.1 触发顺序

CREATE OR REPLACE TRIGGER trg_emp_1
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
  DBMS_OUTPUT.PUT_LINE('Trigger 1');
END;
/

CREATE OR REPLACE TRIGGER trg_emp_2
BEFORE INSERT ON employees
FOR EACH ROW
FOLLOWS trg_emp_1
BEGIN
  DBMS_OUTPUT.PUT_LINE('Trigger 2');
END;
/

7. ENABLE/DISABLE

ALTER TRIGGER trg_emp_audit DISABLE;
ALTER TRIGGER trg_emp_audit ENABLE;

ALTER TABLE employees DISABLE ALL TRIGGERS;
ALTER TABLE employees ENABLE ALL TRIGGERS;

8. 触发器状态

SELECT 
  trigger_name, 
  trigger_type, 
  triggering_event, 
  status
FROM user_triggers
WHERE table_name = 'EMPLOYEES';

9. 触发器源代码

SELECT trigger_body FROM user_triggers WHERE trigger_name = 'TRG_EMP_AUDIT';

10. 触发器管理

10.1 编译

ALTER TRIGGER trg_emp_audit COMPILE;

10.2 删除

DROP TRIGGER trg_emp_audit;

11. 性能影响

11.1 DML 开销

- 行级触发器:每行执行
- 大量 DML:累积开销
- 谨慎使用

11.2 替代方案

- 默认值:DEFAULT
- 序列:12c+ IDENTITY
- 审计:统一审计
- 业务逻辑:存储过程

11.3 复合触发器

- 减少:BEFORE/AFTER STATEMENT
- 性能:比独立行级好

12. 自治事务

12.1 审计

CREATE OR REPLACE TRIGGER trg_audit
AFTER INSERT ON employees
FOR EACH ROW
DECLARE
  PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
  INSERT INTO audit_table (table_name, action, row_id, action_time)
  VALUES ('EMPLOYEES', 'INSERT', :new.id, SYSTIMESTAMP);
  COMMIT;
END;
/

12.2 注意

- 独立事务
- 主事务失败不影响
- COMMIT 必须

13. 常见坑与排错

13.1 变异表

ORA-04091: table ... is mutating
- 行级触发器不能查询/修改当前表
- 用复合触发器或 AFTER STATEMENT

13.2 递归

- 触发器调用导致自身
- ORA-00036
- 避免自调用

13.3 性能

- 大量 DML
- 行级触发器
- 替代为过程

14. 最佳实践

  1. 谨慎使用:性能
  2. 复合触发器:11g+
  3. 避免变异表:复合
  4. 自治事务审计:独立
  5. FOLLOWS 控制顺序:12c+
  6. 替代方案优先:DEFAULT/IDENTITY
  7. DDL 审计:管理
  8. 监控使用:禁用无用
  9. 测试:业务
  10. 文档化:设计

15. 参考资料

[1] Oracle Database PL/SQL Language Reference 19c, “Triggers” https://docs.oracle.com/en/database/oracle/oracle-database/19/lnpls/triggers.html