Oracle存储过程与触发器的详细介绍

下面为你系统、清晰、实用 地介绍 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 程序
  • 当特定事件(如 INSERTUPDATEDELETE)发生时,自动执行

✅ 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 只读)
  • INSERTINGUPDATINGDELETING:布尔函数,判断当前操作类型

✅ 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 中实现高效、安全、自动化数据管理的核心能力。

相关推荐
devmoon6 小时前
在 Polkadot Runtime 中添加多个 Pallet 实例实战指南
java·开发语言·数据库·web3·区块链·波卡
认真的薛薛6 小时前
数据库-sql语句
数据库·sql·oracle
爱学英语的程序员6 小时前
面试官:你了解过哪些数据库?
java·数据库·spring boot·sql·mysql·mybatis
·云扬·7 小时前
MySQL Redo Log落盘机制深度解析
数据库·mysql
用户982863025688 小时前
pg内核实现细节
数据库
飞升不如收破烂~8 小时前
Redis 分布式锁+接口幂等性使用+当下流行的限流方案「落地实操」+用户连续点击两下按钮的解决方案自用总结
数据库·redis·分布式
workflower8 小时前
业务需求-假设场景
java·数据库·测试用例·集成测试·需求分析·模块测试·软件需求
亓才孓8 小时前
[JDBC]基于三层架构和MVC架构的JDBCTools
数据库
IT邦德8 小时前
RPM包快速安装Oracle26ai
数据库·oracle
Dovis(誓平步青云)8 小时前
《滑动窗口算法:从 “暴力遍历” 到 “线性高效” 的思维跃迁》
运维·服务器·数据库·算法