行级与语句级
- 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、删表、清空表、改结构、触发器和存储程序不允许执行。请在独立学习数据库中练习。
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、删表、清空表、改结构、触发器和存储程序不允许执行。请在独立学习数据库中练习。
变异表问题
行级触发器里不允许查询或修改「正在被这条 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、删表、清空表、改结构、触发器和存储程序不允许执行。请在独立学习数据库中练习。
触发器的成本与注意事项
- 触发器对应用是「隐式」的,容易造成排查困难,新人接手时常常找不到逻辑在哪
- 行级触发器在大批量 DML 时开销明显(每行一次 PL/SQL 调用)
- 触发器里的异常会导致整个 DML 失败
- 触发器里的自治事务(PRAGMA AUTONOMOUS_TRANSACTION)可用于记录日志而不影响主事务
- 用 ALTER TRIGGER ... DISABLE 可以在批量导入时临时关掉
- 查询触发器定义:SELECT trigger_name, status FROM user_triggers;