下面为你系统、清晰、实用 地介绍 Oracle 数据库中的 触发器(Trigger) 和 存储过程(Stored Procedure) ,包括它们的概念、用途、语法、编写方法、注意事项 ,并附上可直接运行的示例代码。
一、存储过程(Stored Procedure)
✅ 1. 什么是存储过程?
- 存储过程是一组预编译的 SQL 语句和 PL/SQL 逻辑,存储在数据库中。
- 可被应用程序多次调用,提高性能、减少网络传输、增强安全性。
✅ 2. 核心特点
| 特点 | 说明 |
|---|---|
| 可重复调用 | 一次编写,多次执行 |
| 参数化 | 支持 IN(输入)、OUT(输出)、IN OUT(双向)参数 |
| 事务控制 | 可包含 COMMIT / ROLLBACK(但通常由调用者控制) |
| 权限封装 | 用户无需直接访问表,只需有执行存储过程的权限 |
✅ 3. 基本语法
sql
CREATE [OR REPLACE] PROCEDURE procedure_name (
param1 IN datatype,
param2 OUT datatype,
param3 IN OUT datatype
)
IS
-- 声明局部变量(可选)
v_count NUMBER;
BEGIN
-- 执行逻辑:SQL + PL/SQL
SELECT COUNT(*) INTO v_count FROM employees WHERE department_id = param1;
IF v_count > 0 THEN
param2 := '部门存在';
ELSE
param2 := '部门不存在';
END IF;
EXCEPTION
WHEN OTHERS THEN
param2 := '错误: ' || SQLERRM;
RAISE; -- 可选择是否抛出异常
END;
/
🔔 注意:
CREATE OR REPLACE:如果已存在则覆盖/:在 SQL*Plus 或某些客户端中表示结束(PL/SQL 块结束符)
✅ 4. 调用存储过程
方式 1:在 PL/SQL 块中调用
sql
DECLARE
result VARCHAR2(100);
BEGIN
check_dept_exists(10, result); -- 调用
DBMS_OUTPUT.PUT_LINE(result);
END;
/
方式 2:在 SQL*Plus 或 SQL Developer 中使用 EXEC
sql
VAR msg VARCHAR2(100);
EXEC check_dept_exists(10, :msg);
PRINT msg;
✅ 5. 实用示例:插入员工并返回 ID
sql
CREATE OR REPLACE PROCEDURE insert_employee (
p_name IN VARCHAR2,
p_salary IN NUMBER,
p_dept_id IN NUMBER,
p_emp_id OUT NUMBER
)
IS
BEGIN
INSERT INTO employees (name, salary, department_id)
VALUES (p_name, p_salary, p_dept_id)
RETURNING emp_id INTO p_emp_id; -- 返回自增ID(假设emp_id是序列生成)
COMMIT; -- 注意:生产环境通常不在此提交
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
p_emp_id := -1;
ROLLBACK;
WHEN OTHERS THEN
p_emp_id := -2;
ROLLBACK;
RAISE;
END;
/
二、触发器(Trigger)
✅ 1. 什么是触发器?
- 触发器是与表/视图/数据库事件关联的 PL/SQL 程序。
- 当特定事件(如
INSERT、UPDATE、DELETE)发生时,自动执行。
✅ 2. 触发器类型
| 分类维度 | 类型 | 说明 |
|---|---|---|
| 触发时机 | BEFORE / AFTER |
在操作前或后执行 |
| 触发粒度 | ROW / STATEMENT |
每行触发 or 整条语句触发(默认是语句级) |
| 触发事件 | INSERT / UPDATE / DELETE |
可组合,如 INSERT OR UPDATE |
| 作用对象 | 表、视图、数据库(DDL/DCL) | 最常用的是表级触发器 |
⚠️ 重要区别:
- 行级触发器 :用
FOR EACH ROW,可访问:OLD和:NEW伪记录- 语句级触发器 :无
:OLD/:NEW,只执行一次
✅ 3. 基本语法(表级行触发器)
sql
CREATE [OR REPLACE] TRIGGER trigger_name
BEFORE | AFTER
INSERT | UPDATE | DELETE
ON table_name
[FOR EACH ROW]
DECLARE
-- 可声明变量
BEGIN
-- 触发逻辑
IF INSERTING THEN
:NEW.created_time := SYSDATE; -- 自动填充创建时间
ELSIF UPDATING THEN
:NEW.updated_time := SYSDATE; -- 自动更新时间
END IF;
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20001, '触发器错误: ' || SQLERRM);
END;
/
🔑 关键点:
:NEW.column:新值(INSERT/UPDATE可写):OLD.column:旧值(UPDATE/DELETE只读)INSERTING、UPDATING、DELETING:布尔函数,判断当前操作类型
✅ 4. 实用示例
示例 1:自动审计日志(行级触发器)
sql
-- 创建审计表
CREATE TABLE emp_audit (
audit_id NUMBER GENERATED ALWAYS AS IDENTITY,
emp_id NUMBER,
action VARCHAR2(10), -- 'INSERT', 'UPDATE', 'DELETE'
old_salary NUMBER,
new_salary NUMBER,
changed_by VARCHAR2(30),
changed_at TIMESTAMP
);
-- 创建触发器
CREATE OR REPLACE TRIGGER trg_emp_audit
AFTER INSERT OR UPDATE OR DELETE ON employees
FOR EACH ROW
BEGIN
IF INSERTING THEN
INSERT INTO emp_audit (emp_id, action, new_salary, changed_by, changed_at)
VALUES (:NEW.emp_id, 'INSERT', :NEW.salary, USER, SYSTIMESTAMP);
ELSIF UPDATING THEN
INSERT INTO emp_audit (emp_id, action, old_salary, new_salary, changed_by, changed_at)
VALUES (:OLD.emp_id, 'UPDATE', :OLD.salary, :NEW.salary, USER, SYSTIMESTAMP);
ELSIF DELETING THEN
INSERT INTO emp_audit (emp_id, action, old_salary, changed_by, changed_at)
VALUES (:OLD.emp_id, 'DELETE', :OLD.salary, USER, SYSTIMESTAMP);
END IF;
END;
/
示例 2:防止工资降低(行级 BEFORE 触发器)
sql
CREATE OR REPLACE TRIGGER trg_prevent_salary_cut
BEFORE UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
IF :NEW.salary < :OLD.salary THEN
RAISE_APPLICATION_ERROR(-20002, '工资不能降低!');
END IF;
END;
/
三、触发器 vs 存储过程:关键区别
| 特性 | 存储过程 | 触发器 |
|---|---|---|
| 调用方式 | 显式调用(CALL/EXEC) | 隐式自动触发 |
| 参数 | 支持 IN/OUT 参数 | 无参数(通过 :OLD/:NEW 访问数据) |
| 事务控制 | 可包含 COMMIT(不推荐) | 不能 COMMIT/ROLLBACK(属于当前事务) |
| 用途 | 业务逻辑封装 | 数据完整性、审计、自动维护 |
| 调试难度 | 较易 | 较难(自动执行,不易追踪) |
💡 最佳实践:
- 用存储过程 实现业务逻辑
- 用触发器 实现数据约束、审计、自动字段维护
- 避免在触发器中写复杂逻辑(影响性能且难排查)
四、常见问题与注意事项
❗ 1. 触发器中的"变异表"错误(ORA-04091)
- 原因:在行级触发器中查询正在被修改的表。
- 解决方案 :
- 使用 复合触发器(Compound Trigger)
- 或将逻辑移到存储过程中,由应用调用
❗ 2. 性能影响
- 触发器会增加 DML 操作的开销,避免在高频表上使用复杂触发器
❗ 3. 权限问题
- 触发器以定义者权限(Definer's Rights) 运行(默认),不是调用者权限
❗ 4. 禁用/启用触发器
sql
ALTER TRIGGER trg_emp_audit DISABLE; -- 禁用
ALTER TRIGGER trg_emp_audit ENABLE; -- 启用
ALTER TABLE employees DISABLE ALL TRIGGERS; -- 禁用表上所有触发器
✅ 总结
| 组件 | 核心价值 | 典型场景 |
|---|---|---|
| 存储过程 | 封装可复用业务逻辑 | 批量处理、API 接口、复杂计算 |
| 触发器 | 自动化数据维护 | 审计日志、自动时间戳、数据校验 |
🌟 记住:
- 存储过程 = 你主动调用的"函数"
- 触发器 = 数据库自动执行的"监听器"
掌握这两者,你就拥有了在 Oracle 中实现高效、安全、自动化数据管理的核心能力。