Oracle触发器用法实例详解
触发器是Oracle数据库中一种特殊的存储过程,它会在特定事件发生时自动执行,不需要手动调用。当对表执行INSERT、UPDATE、DELETE等操作时,触发器可以被触发,常用于数据校验、审计日志、数据同步等场景。本文将详细介绍Oracle触发器的类型、语法和常见使用实例。
一、触发器的基本分类
根据触发事件和触发时机的不同,Oracle触发器可以分为以下几类:
DML触发器:针对表上的INSERT、UPDATE、DELETE操作触发,是最常用的触发器类型。
INSTEAD OF触发器:用于视图,当对视图执行DML操作时替代原来的操作,实现视图的可更新性。
系统触发器:针对数据库系统事件触发,比如登录、注销、数据库启动、关闭等。
二、DML触发器的语法结构
DML触发器是最常用的触发器类型,基本语法如下:
CREATE [OR REPLACE] TRIGGER 触发器名称
{BEFORE | AFTER} {INSERT | UPDATE | DELETE}
ON 表名称
[REFERENCING OLD AS old NEW AS new]
[FOR EACH ROW] -- 行级触发器,不加则为语句级触发器
[WHEN (条件)]
DECLARE
-- 变量声明部分
BEGIN
-- 触发器逻辑
EXCEPTION
-- 异常处理部分
END 触发器名称;
/语法说明:
BEFORE/AFTER:指定触发器在操作执行前还是执行后触发。INSERT/UPDATE/DELETE:指定触发事件,UPDATE可以指定列,比如UPDATE OF 列名1,列名2。REFERENCING:用于给旧值和新值起别名,默认旧值别名为:old,新值别名为:new。FOR EACH ROW:行级触发器,每影响一行数据就触发一次;如果不加该子句,则为语句级触发器,无论影响多少行数据只触发一次。WHEN:可选条件,只有满足条件时才执行触发器逻辑。
三、常见实例演示
3.1 数据插入前自动填充创建时间(BEFORE INSERT行级触发器)
假设我们有一个用户表user_info,包含用户ID、用户名、创建时间等字段,需要在插入数据时自动填充创建时间为当前时间。
首先创建测试表:
CREATE TABLE user_info ( user_id NUMBER PRIMARY KEY, user_name VARCHAR2(50) NOT NULL, create_time DATE );
创建触发器:
CREATE OR REPLACE TRIGGER trg_user_info_insert BEFORE INSERT ON user_info FOR EACH ROW BEGIN -- 如果插入时没有指定创建时间,自动填充当前时间 IF :new.create_time IS NULL THEN :new.create_time := SYSDATE; END IF; END trg_user_info_insert; /
测试插入数据,不指定create_time字段:
INSERT INTO user_info (user_id, user_name) VALUES (1, '张三'); COMMIT; -- 查询数据,创建时间会自动填充为当前时间 SELECT user_id, user_name, create_time FROM user_info;
3.2 数据修改审计(AFTER UPDATE行级触发器)
假设我们需要对user_info表的用户名修改操作进行审计,记录修改前的用户名、修改后的用户名、修改时间和操作人。
首先创建审计表:
CREATE TABLE user_info_audit ( audit_id NUMBER PRIMARY KEY, user_id NUMBER, old_user_name VARCHAR2(50), new_user_name VARCHAR2(50), update_time DATE, operator VARCHAR2(50) ); -- 创建审计表主键序列 CREATE SEQUENCE seq_user_audit_id START WITH 1 INCREMENT BY 1;
创建触发器:
CREATE OR REPLACE TRIGGER trg_user_info_update AFTER UPDATE OF user_name ON user_info FOR EACH ROW DECLARE v_operator VARCHAR2(50); BEGIN -- 获取当前操作人,这里模拟从上下文获取,实际场景可根据需求调整 v_operator := USER; -- 插入审计记录 INSERT INTO user_info_audit (audit_id, user_id, old_user_name, new_user_name, update_time, operator) VALUES (seq_user_audit_id.NEXTVAL, :old.user_id, :old.user_name, :new.user_name, SYSDATE, v_operator); END trg_user_info_update; /
测试修改用户名:
UPDATE user_info SET user_name = '李四' WHERE user_id = 1; COMMIT; -- 查询审计表,会看到对应的修改记录 SELECT audit_id, user_id, old_user_name, new_user_name, update_time, operator FROM user_info_audit;
3.3 阻止非法删除操作(BEFORE DELETE语句级触发器)
假设我们不允许删除user_info表中所有数据,只允许删除指定条件的行,可以通过语句级触发器实现。
创建触发器:
CREATE OR REPLACE TRIGGER trg_user_info_delete BEFORE DELETE ON user_info BEGIN -- 如果是删除全表数据(没有WHERE条件),则抛出异常阻止操作 -- 注意:语句级触发器无法获取受影响的行信息,这里通过判断是否删除所有行来实现,实际场景可结合业务调整 IF SQL%ROWCOUNT = 0 THEN NULL; ELSE -- 这里简单模拟阻止全表删除,实际更严谨的判断需要结合业务,比如检查是否有条件过滤 RAISE_APPLICATION_ERROR(-20001, '不允许删除全表数据,请指定删除条件'); END IF; END trg_user_info_delete; /
测试删除操作:
-- 指定条件删除,正常执行 DELETE FROM user_info WHERE user_id = 1; COMMIT; -- 尝试删除全表数据,会触发异常 DELETE FROM user_info; /
3.4 INSTEAD OF触发器实现可更新视图
当我们创建一个关联多表的视图时,默认可能无法直接对视图执行DML操作,此时可以使用INSTEAD OF触发器实现视图更新。
首先创建两个基础表并插入测试数据:
CREATE TABLE dept ( dept_id NUMBER PRIMARY KEY, dept_name VARCHAR2(50) ); CREATE TABLE emp ( emp_id NUMBER PRIMARY KEY, emp_name VARCHAR2(50), dept_id NUMBER, CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) REFERENCES dept(dept_id) ); INSERT INTO dept VALUES (1, '技术部'); INSERT INTO dept VALUES (2, '市场部'); INSERT INTO emp VALUES (1, '张三', 1); INSERT INTO emp VALUES (2, '李四', 1); COMMIT;
创建关联视图:
CREATE VIEW v_emp_dept AS SELECT e.emp_id, e.emp_name, d.dept_id, d.dept_name FROM emp e JOIN dept d ON e.dept_id = d.dept_id;
创建INSTEAD OF触发器,实现对视图的插入操作:
CREATE OR REPLACE TRIGGER trg_v_emp_dept_insert INSTEAD OF INSERT ON v_emp_dept FOR EACH ROW DECLARE v_dept_count NUMBER; BEGIN -- 检查部门是否存在,不存在则先插入部门 SELECT COUNT(*) INTO v_dept_count FROM dept WHERE dept_id = :new.dept_id; IF v_dept_count = 0 THEN INSERT INTO dept (dept_id, dept_name) VALUES (:new.dept_id, :new.dept_name); END IF; -- 插入员工数据 INSERT INTO emp (emp_id, emp_name, dept_id) VALUES (:new.emp_id, :new.emp_name, :new.dept_id); END trg_v_emp_dept_insert; /
测试通过视图插入数据:
INSERT INTO v_emp_dept VALUES (3, '王五', 3, '人事部'); COMMIT; -- 查询视图和基础表,都会看到新增的数据 SELECT * FROM v_emp_dept; SELECT * FROM dept; SELECT * FROM emp;
四、触发器的注意事项
触发器是隐式执行的,过多或复杂的触发器会影响数据库性能,尤其是大批量数据操作时。
触发器不能返回任何值,也不能包含事务控制语句(如COMMIT、ROLLBACK),因为触发器的事务和触发它的DML语句属于同一个事务。
行级触发器中不能使用查询当前表的操作,否则会触发变异表错误(ORA-04091),如果需要操作当前表,可以考虑使用自治事务,或者调整业务逻辑。
可以通过
SELECT * FROM USER_TRIGGERS;查询当前用户下的所有触发器,通过DROP TRIGGER 触发器名称;删除触发器。触发器的执行顺序:如果有多个同类型的触发器(比如都是BEFORE INSERT行级触发器),执行顺序不确定,Oracle不保证多个触发器的执行顺序,因此尽量不要对同一个表和事件创建多个同类型触发器。
五、总结
Oracle触发器是数据库中非常实用的功能,能够帮助我们实现很多自动化的业务需求,比如数据自动填充、操作审计、数据校验等。使用时需要根据场景选择合适的触发器类型,注意触发时机和触发粒度,避免过度使用导致性能问题。合理运用触发器可以减少业务代码的重复逻辑,提升数据的一致性和完整性。