必须用before delete,因old伪记录仅在此阶段完整可用;after阶段虽可读old,但级联操作可能导致数据不可靠;需避免触发表自查询引发ora-04091错误,直接引用:old字段值插入备份表即可。

Oracle触发器里用BEFORE DELETE还是AFTER DELETE?
必须用 BEFORE DELETE。因为你要备份的是“被删之前”的原始数据,OLD 伪记录只在 BEFORE 阶段完整可用;到了 AFTER 阶段虽然 OLD 还能读,但若表上有级联操作或业务逻辑已清理关联数据,可能已经不可靠。
常见错误现象:ORA-04091: table is mutating —— 这通常是因为在 BEFORE DELETE 触发器里又去查了当前表(比如写 SELECT * FROM my_table WHERE id = :OLD.id),Oracle 禁止这种自查询。解决办法是:不查原表,直接用 :OLD.column_name 取值。
- 不要在触发器里对触发表做任何
SELECT、UPDATE、DELETE - 所有字段值都从
:OLD直接引用,比如:OLD.emp_id、:OLD.salary - 如果备份表要加操作类型和时间戳,用
'DELETE'字面量和SYSDATE即可
备份表结构怎么设计才不踩坑?
备份表字段必须和原表完全一致(含类型、长度、是否允许 NULL),否则插入时会报 ORA-00947: not enough values 或隐式转换失败。额外字段建议只加两个:op_type VARCHAR2(10) 和 op_time DATE,别加主键、索引或外键——这些会拖慢删除性能,也无实际审计价值。
容易忽略的点:NUMBER 类型字段如果原表定义为 NUMBER(10,2),备份表也得一样,不能简写成 NUMBER;VARCHAR2 长度少 1 个字节都会导致截断警告(尤其在严格模式下)。
- 建表语句推荐用
CREATE TABLE emp_backup AS SELECT *, 'DELETE' op_type, SYSDATE op_time FROM emp WHERE 1=0快速复制结构(注意去掉约束和索引) - 手动补全字段时,用
DESC emp核对每个字段的NULL?和Type - 避免用
LONG或LOB字段——:OLD无法直接访问它们,会报ORA-04091或静默丢弃
触发器里怎么安全插入备份数据?
核心原则:单条 INSERT INTO ... VALUES (...),所有值来自 :OLD 和字面量。不能用子查询、不能嵌套 SELECT,也不能调用函数(除非确定是纯函数且无副作用)。
示例(假设原表 emp(emp_id, ename, salary, deptno)):
CREATE OR REPLACE TRIGGER emp_bkup_trg BEFORE DELETE ON emp FOR EACH ROW BEGIN INSERT INTO emp_backup (emp_id, ename, salary, deptno, op_type, op_time) VALUES (:OLD.emp_id, :OLD.ename, :OLD.salary, :OLD.deptno, 'DELETE', SYSDATE); END;
- 别写
INSERT INTO emp_backup SELECT ... FROM emp—— 这是典型mutating table错误源头 - 如果原表有默认值字段(如
created_by VARCHAR2(30) DEFAULT USER),备份表里对应字段不能依赖默认,必须显式传:OLD.created_by - 遇到
DATE字段,直接用:OLD.hire_date,别转TO_DATE—— 类型已匹配,多此一举反而可能出错
为什么删完数据却没进备份表?
最常见原因是触发器被禁用了,或者用户没有对备份表的 INSERT 权限。Oracle 不会在触发器执行失败时自动回滚主操作(比如 DELETE),而是静默吞掉错误 —— 表现就是行删了,备份表空空如也。
排查步骤:
- 查触发器状态:
SELECT trigger_name, status FROM user_triggers WHERE table_name = 'EMP';,确保是ENABLED - 检查权限:
SELECT * FROM user_tab_privs WHERE table_name = 'EMP_BACKUP' AND privilege = 'INSERT'; - 临时加日志(仅调试):
DBMS_OUTPUT.PUT_LINE('Backup triggered for ' || :OLD.emp_id);,再配SET SERVEROUTPUT ON看是否执行到这步 - 特别注意:如果原表是分区表,且触发器定义在子分区上,父表
DELETE可能根本不会触发它
复杂点在于:触发器本身不报错,也不抛异常,你得主动验证它的存在感。上线前务必用 DELETE 一条测试数据 + 查备份表双确认。











