【数据库】【Oracle】PL/SQL 存储过程与函数

Oracle PL/SQL 存储过程与函数完全指南

存储过程(Procedure)和函数(Function)是 PL/SQL 的核心可执行单元,用于封装业务逻辑、提升性能、增强安全性和代码复用。


一、核心概念与区别

1.1 存储过程 vs 函数

特性 存储过程 (Procedure) 函数 (Function)
返回值 通过 OUT 参数返回,可返回多个值 必须返回单个值(通过 RETURN
调用方式 EXECUTE/CALL 或 PL/SQL 块中调用 可在 SQL 语句中直接调用
用途 执行操作(插入、更新、批量处理) 计算并返回值(如公式、转换)
事务控制 可包含 COMMIT/ROLLBACK 通常不包含事务控制
性能 适合复杂业务逻辑 适合计算密集型操作

1.2 基本语法结构

sql 复制代码
-- 存储过程语法
CREATE [OR REPLACE] PROCEDURE 过程名 (
    参数1 [模式] 数据类型,
    参数2 [模式] 数据类型
) [AUTHID {DEFINER | CURRENT_USER}] -- 权限模型
IS|AS
    -- 声明部分(变量、游标、类型)
    变量声明;
BEGIN
    -- 执行部分
    可执行语句;
EXCEPTION
    -- 异常处理部分
    异常处理;
END [过程名];
/

-- 函数语法
CREATE [OR REPLACE] FUNCTION 函数名 (
    参数1 [模式] 数据类型
) RETURN 返回数据类型
IS|AS
    变量声明;
BEGIN
    RETURN 返回值;
EXCEPTION
    异常处理;
END [函数名];
/

二、创建与调用

2.1 创建存储过程

sql 复制代码
-- 场景:调整员工薪水并记录日志
CREATE OR REPLACE PROCEDURE adjust_employee_salary (
    p_emp_id IN NUMBER,           -- 员工ID(输入参数)
    p_percent IN NUMBER,          -- 调整百分比(输入参数)
    p_new_salary OUT NUMBER,      -- 新薪水(输出参数)
    p_message OUT VARCHAR2        -- 消息(输出参数)
) AUTHID DEFINER
IS
    -- 声明变量
    v_old_salary employees.salary%TYPE;
    v_emp_name VARCHAR2(100);
BEGIN
    -- 1. 查询当前薪水
    SELECT salary, first_name || ' ' || last_name
    INTO v_old_salary, v_emp_name
    FROM employees
    WHERE employee_id = p_emp_id;
    
    -- 2. 计算新薪水
    p_new_salary := v_old_salary * (1 + p_percent / 100);
    
    -- 3. 更新薪水
    UPDATE employees
    SET salary = p_new_salary
    WHERE employee_id = p_emp_id;
    
    -- 4. 记录日志
    INSERT INTO salary_log (emp_id, old_salary, new_salary, change_date)
    VALUES (p_emp_id, v_old_salary, p_new_salary, SYSDATE);
    
    -- 5. 设置返回消息
    p_message := '员工 ' || v_emp_name || ' 薪水已从 ' || v_old_salary || ' 调整为 ' || p_new_salary;
    
    -- 6. 提交事务
    COMMIT;
    
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        p_message := '错误:员工ID ' || p_emp_id || ' 不存在';
        ROLLBACK;
    WHEN OTHERS THEN
        p_message := '错误:' || SQLERRM;
        ROLLBACK;
END adjust_employee_salary;
/

2.2 调用存储过程

sql 复制代码
-- 方式1:匿名块调用
DECLARE
    v_new_salary NUMBER;
    v_message VARCHAR2(200);
BEGIN
    adjust_employee_salary(p_emp_id => 101, 
                           p_percent => 10, 
                           p_new_salary => v_new_salary, 
                           p_message => v_message);
    DBMS_OUTPUT.PUT_LINE(v_message);
    DBMS_OUTPUT.PUT_LINE('新薪水:' || v_new_salary);
END;
/

-- 方式2:EXECUTE 命令(SQL*Plus)
VARIABLE new_salary NUMBER
VARIABLE message VARCHAR2(200)
EXEC adjust_employee_salary(101, 10, :new_salary, :message);
PRINT new_salary
PRINT message

-- 方式3:JDBC 调用(Java)
CallableStatement cstmt = conn.prepareCall("{call adjust_employee_salary(?, ?, ?, ?)}");
cstmt.setInt(1, 101);
cstmt.setDouble(2, 10);
cstmt.registerOutParameter(3, Types.NUMERIC);
cstmt.registerOutParameter(4, Types.VARCHAR);
cstmt.execute();
double newSalary = cstmt.getDouble(3);
String message = cstmt.getString(4);

三、参数模式详解

3.1 IN 模式(默认)

sql 复制代码
-- 输入参数,只读
CREATE PROCEDURE process_order (
    p_order_id IN NUMBER,      -- 输入订单ID
    p_status OUT VARCHAR2
) IS
BEGIN
    -- p_order_id 可被读取,但不能被修改
    UPDATE orders SET status = 'PROCESSING' WHERE order_id = p_order_id;
    p_status := '处理完成';
END;
/

3.2 OUT 模式

sql 复制代码
-- 输出参数,用于返回值
CREATE PROCEDURE get_employee_info (
    p_emp_id IN NUMBER,
    p_name OUT VARCHAR2,
    p_salary OUT NUMBER,
    p_hire_date OUT DATE
) IS
BEGIN
    SELECT first_name || ' ' || last_name, salary, hire_date
    INTO p_name, p_salary, p_hire_date
    FROM employees
    WHERE employee_id = p_emp_id;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        p_name := NULL;
        p_salary := NULL;
        p_hire_date := NULL;
END;
/

3.3 IN OUT 模式

sql 复制代码
-- 输入输出参数,既可读也可写
CREATE PROCEDURE swap_values (
    p_value1 IN OUT NUMBER,
    p_value2 IN OUT NUMBER
) IS
    v_temp NUMBER;
BEGIN
    v_temp := p_value1;
    p_value1 := p_value2;
    p_value2 := v_temp;
END;
/

-- 调用
DECLARE
    a NUMBER := 10;
    b NUMBER := 20;
BEGIN
    DBMS_OUTPUT.PUT_LINE('交换前:a=' || a || ', b=' || b);
    swap_values(a, b);
    DBMS_OUTPUT.PUT_LINE('交换后:a=' || a || ', b=' || b);
END;
/

3.4 参数默认值

sql 复制代码
CREATE OR REPLACE PROCEDURE create_employee (
    p_first_name IN VARCHAR2,
    p_last_name IN VARCHAR2,
    p_salary IN NUMBER DEFAULT 5000,  -- 默认值
    p_department_id IN NUMBER DEFAULT 50
) IS
BEGIN
    INSERT INTO employees (employee_id, first_name, last_name, salary, department_id)
    VALUES (emp_seq.NEXTVAL, p_first_name, p_last_name, p_salary, p_department_id);
    COMMIT;
END;
/

-- 调用(使用默认值)
EXEC create_employee('John', 'Doe');
-- 调用(覆盖默认值)
EXEC create_employee('Jane', 'Smith', 8000, 60);

四、创建与调用函数

4.1 创建函数

sql 复制代码
-- 场景:根据员工ID计算年薪(含奖金)
CREATE OR REPLACE FUNCTION calculate_annual_income (
    p_emp_id IN NUMBER,
    p_include_bonus IN BOOLEAN DEFAULT TRUE
) RETURN NUMBER
IS
    v_salary employees.salary%TYPE;
    v_commission employees.commission_pct%TYPE;
    v_annual_income NUMBER;
BEGIN
    -- 查询薪水和提成比例
    SELECT salary, commission_pct
    INTO v_salary, v_commission
    FROM employees
    WHERE employee_id = p_emp_id;
    
    -- 计算年收入
    IF p_include_bonus AND v_commission IS NOT NULL THEN
        v_annual_income := v_salary * 12 * (1 + v_commission);
    ELSE
        v_annual_income := v_salary * 12;
    END IF;
    
    RETURN v_annual_income;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RETURN NULL;  -- 函数必须返回值
    WHEN OTHERS THEN
        RETURN -1;    -- 错误标识
END calculate_annual_income;
/

4.2 调用函数

sql 复制代码
-- 方式1:在 SQL 语句中调用(函数的核心优势)
SELECT 
    employee_id,
    first_name,
    salary,
    calculate_annual_income(employee_id, TRUE) AS annual_income
FROM employees
WHERE calculate_annual_income(employee_id) > 200000;

-- 方式2:在 PL/SQL 块中调用
DECLARE
    v_income NUMBER;
BEGIN
    v_income := calculate_annual_income(101, FALSE);
    DBMS_OUTPUT.PUT_LINE('年收入:' || v_income);
END;
/

-- 方式3:在 WHERE 子句中调用
SELECT * FROM employees
WHERE calculate_annual_income(employee_id) > (SELECT AVG(salary*12) FROM employees);

五、高级特性

5.1 异常处理(Exception Handling)

sql 复制代码
-- 预定义异常
CREATE OR REPLACE PROCEDURE safe_delete_employee (
    p_emp_id IN NUMBER
) IS
BEGIN
    DELETE FROM employees WHERE employee_id = p_emp_id;
    
    IF SQL%NOTFOUND THEN
        RAISE_APPLICATION_ERROR(-20001, '员工 ' || p_emp_id || ' 不存在');
    END IF;
    
    COMMIT;
    
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('没有找到数据');
    WHEN TOO_MANY_ROWS THEN
        DBMS_OUTPUT.PUT_LINE('返回多行,但期望单行');
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('错误代码:' || SQLCODE);
        DBMS_OUTPUT.PUT_LINE('错误消息:' || SQLERRM);
        ROLLBACK;
END;
/

-- 自定义异常
CREATE OR REPLACE PROCEDURE update_salary_check (
    p_emp_id IN NUMBER,
    p_new_salary IN NUMBER
) IS
    e_salary_too_high EXCEPTION;  -- 声明自定义异常
    PRAGMA EXCEPTION_INIT(e_salary_too_high, -20002);  -- 关联错误码
BEGIN
    IF p_new_salary > 20000 THEN
        RAISE e_salary_too_high;  -- 抛出自定义异常
    END IF;
    
    UPDATE employees SET salary = p_new_salary WHERE employee_id = p_emp_id;
    COMMIT;
    
EXCEPTION
    WHEN e_salary_too_high THEN
        DBMS_OUTPUT.PUT_LINE('错误:新薪资不能超过20000');
        ROLLBACK;
END;
/

5.2 游标(Cursor)

sql 复制代码
-- 显式游标处理多行数据
CREATE OR REPLACE PROCEDURE bulk_raise_salary (
    p_dept_id IN NUMBER,
    p_percent IN NUMBER
) IS
    -- 声明游标
    CURSOR emp_cursor IS
        SELECT employee_id, salary FROM employees 
        WHERE department_id = p_dept_id 
        FOR UPDATE;  -- 加锁防止并发修改
    
    -- 记录类型
    emp_rec emp_cursor%ROWTYPE;
BEGIN
    OPEN emp_cursor;
    
    LOOP
        FETCH emp_cursor INTO emp_rec;
        EXIT WHEN emp_cursor%NOTFOUND;  -- 退出循环条件
        
        -- 更新薪水
        UPDATE employees 
        SET salary = emp_rec.salary * (1 + p_percent/100)
        WHERE CURRENT OF emp_cursor;  -- 定位当前游标行
        
        DBMS_OUTPUT.PUT_LINE('员工 ' || emp_rec.employee_id || ' 已调整');
    END LOOP;
    
    CLOSE emp_cursor;
    COMMIT;
    
EXCEPTION
    WHEN OTHERS THEN
        CLOSE emp_cursor;
        ROLLBACK;
        RAISE;
END;
/

-- 游标 FOR 循环(简化)
CREATE OR REPLACE PROCEDURE process_high_earners IS
BEGIN
    FOR emp_rec IN (SELECT employee_id, salary FROM employees WHERE salary > 10000)
    LOOP
        INSERT INTO high_earner_log VALUES (emp_rec.employee_id, emp_rec.salary, SYSDATE);
    END LOOP;
    COMMIT;
END;
/

5.3 自治事务(Autonomous Transaction)

sql 复制代码
-- 日志记录不受主事务影响
CREATE OR REPLACE PROCEDURE log_message (
    p_message IN VARCHAR2
) IS
    PRAGMA AUTONOMOUS_TRANSACTION;  -- 声明自治事务
BEGIN
    INSERT INTO message_log (message, log_time) VALUES (p_message, SYSDATE);
    COMMIT;  -- 独立提交,不影响主事务
END;
/

-- 主事务回滚,但日志已提交
CREATE OR REPLACE PROCEDURE main_transaction IS
BEGIN
    INSERT INTO orders VALUES (101, 5000);
    log_message('订单 101 已创建');  -- 自治事务已提交
    
    ROLLBACK;  -- 订单被回滚,但日志保留
END;
/

5.4 动态 SQL(EXECUTE IMMEDIATE)

sql 复制代码
-- 场景:动态表名查询
CREATE OR REPLACE FUNCTION dynamic_query (
    p_table_name IN VARCHAR2,
    p_id IN NUMBER
) RETURN VARCHAR2
IS
    v_sql VARCHAR2(1000);
    v_result VARCHAR2(100);
BEGIN
    v_sql := 'SELECT name FROM ' || p_table_name || ' WHERE id = :id';
    
    EXECUTE IMMEDIATE v_sql
    INTO v_result
    USING p_id;  -- 绑定变量防止 SQL 注入
    
    RETURN v_result;
    
EXCEPTION
    WHEN OTHERS THEN
        RETURN '查询失败:' || SQLERRM;
END;
/

-- 动态 DDL
CREATE OR REPLACE PROCEDURE create_log_table (p_table_name IN VARCHAR2) IS
BEGIN
    EXECUTE IMMEDIATE 'CREATE TABLE ' || p_table_name || '_log (
        id NUMBER GENERATED ALWAYS AS IDENTITY,
        message VARCHAR2(200),
        log_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )';
END;
/

六、包(Package)------代码封装

6.1 包的创建(规范 + 主体)

sql 复制代码
-- 1. 包规范(接口定义)
CREATE OR REPLACE PACKAGE employee_mgmt AS
    -- 常量
    c_max_salary CONSTANT NUMBER := 50000;
    
    -- 类型定义
    TYPE emp_rec_type IS RECORD (
        emp_id NUMBER,
        emp_name VARCHAR2(100),
        salary NUMBER
    );
    
    TYPE emp_tab_type IS TABLE OF emp_rec_type INDEX BY PLS_INTEGER;
    
    -- 函数声明
    FUNCTION calculate_bonus (p_emp_id IN NUMBER) RETURN NUMBER;
    FUNCTION get_employee_info (p_emp_id IN NUMBER) RETURN emp_rec_type;
    
    -- 过程声明
    PROCEDURE hire_employee (
        p_first_name IN VARCHAR2,
        p_last_name IN VARCHAR2,
        p_salary IN NUMBER
    );
    
    PROCEDURE fire_employee (p_emp_id IN NUMBER);
    
END employee_mgmt;
/

-- 2. 包主体(实现)
CREATE OR REPLACE PACKAGE BODY employee_mgmt AS
    
    -- 私有函数(外部不可见)
    FUNCTION validate_salary (p_salary IN NUMBER) RETURN BOOLEAN IS
    BEGIN
        RETURN p_salary BETWEEN 1000 AND c_max_salary;
    END validate_salary;
    
    -- 公有函数实现
    FUNCTION calculate_bonus (p_emp_id IN NUMBER) RETURN NUMBER IS
        v_salary employees.salary%TYPE;
    BEGIN
        SELECT salary INTO v_salary FROM employees WHERE employee_id = p_emp_id;
        RETURN v_salary * 0.1;  -- 奖金为薪水的10%
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            RETURN 0;
    END calculate_bonus;
    
    -- 存储过程实现
    PROCEDURE hire_employee (
        p_first_name IN VARCHAR2,
        p_last_name IN VARCHAR2,
        p_salary IN NUMBER
    ) IS
    BEGIN
        IF NOT validate_salary(p_salary) THEN
            RAISE_APPLICATION_ERROR(-20003, '薪资超出范围');
        END IF;
        
        INSERT INTO employees (employee_id, first_name, last_name, salary, hire_date)
        VALUES (emp_seq.NEXTVAL, p_first_name, p_last_name, p_salary, SYSDATE);
        
        COMMIT;
    END hire_employee;
    
END employee_mgmt;
/

6.2 调用包内程序

sql 复制代码
-- 调用包函数
SELECT employee_mgmt.calculate_bonus(101) FROM dual;

-- 调用包过程
DECLARE
    v_emp_info employee_mgmt.emp_rec_type;
BEGIN
    v_emp_info := employee_mgmt.get_employee_info(102);
    DBMS_OUTPUT.PUT_LINE('姓名:' || v_emp_info.emp_name);
    
    employee_mgmt.hire_employee('Alice', 'Smith', 7500);
END;
/

6.3 包的优势

  • 模块化:逻辑分组,代码组织清晰
  • 性能:首次加载后常驻内存,后续调用更快
  • 封装:公有/私有分离,隐藏实现细节
  • 状态保持:包变量在会话中持续存在
  • 重载:支持同名过程/函数(参数不同)

七、性能优化技巧

7.1 使用 BULK COLLECT 批量操作

sql 复制代码
-- 错误:逐行处理(慢)
CREATE OR REPLACE PROCEDURE slow_update IS
BEGIN
    FOR emp IN (SELECT employee_id, salary FROM employees WHERE department_id = 80)
    LOOP
        UPDATE employees SET salary = salary * 1.1 WHERE employee_id = emp.employee_id;
    END LOOP;
    COMMIT;
END;

-- 正确:批量处理
CREATE OR REPLACE PROCEDURE fast_update IS
    TYPE num_tab IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
    v_emp_ids num_tab;
    v_salaries num_tab;
BEGIN
    -- 批量获取
    SELECT employee_id, salary 
    BULK COLLECT INTO v_emp_ids, v_salaries
    FROM employees WHERE department_id = 80;
    
    -- 批量更新
    FORALL i IN 1..v_emp_ids.COUNT
        UPDATE employees SET salary = v_salaries(i) * 1.1 
        WHERE employee_id = v_emp_ids(i);
    
    COMMIT;
END;

7.2 使用 NOCOPY 提示(减少参数复制开销)

sql 复制代码
-- 对于大集合,使用 NOCOPY 避免拷贝
CREATE OR REPLACE PROCEDURE process_large_collection (
    p_collection IN OUT NOCOPY large_collection_type  -- NOCOPY 提示
) IS
BEGIN
    -- 直接操作原集合,不创建副本
    FOR i IN 1..p_collection.COUNT LOOP
        p_collection(i).status := 'PROCESSED';
    END LOOP;
END;

7.3 避免上下文切换

sql 复制代码
-- 错误:SQL 和 PL/SQL 频繁切换
CREATE OR REPLACE FUNCTION get_department_name (p_dept_id NUMBER) RETURN VARCHAR2 IS
    v_name VARCHAR2(100);
BEGIN
    SELECT department_name INTO v_name FROM departments WHERE department_id = p_dept_id;
    RETURN v_name;
END;

-- 查询时使用
SELECT employee_id, get_department_name(department_id) FROM employees;  -- 低效

-- 正确:纯 SQL 实现
SELECT e.employee_id, d.department_name
FROM employees e JOIN departments d ON e.department_id = d.department_id;

八、调试与监控

8.1 DBMS_OUTPUT 调试

sql 复制代码
SET SERVEROUTPUT ON;  -- 开启输出

CREATE OR REPLACE PROCEDURE debug_demo IS
    v_counter NUMBER := 0;
BEGIN
    FOR rec IN (SELECT employee_id FROM employees WHERE ROWNUM <= 5)
    LOOP
        v_counter := v_counter + 1;
        DBMS_OUTPUT.PUT_LINE('处理第 ' || v_counter || ' 个员工:' || rec.employee_id);
    END LOOP;
END;
/

8.2 使用 DBMS_APPLICATION_INFO

sql 复制代码
-- 在 V$SESSION 中显示进度
CREATE OR REPLACE PROCEDURE long_running_task IS
    v_total NUMBER;
BEGIN
    SELECT COUNT(*) INTO v_total FROM employees;
    
    FOR rec IN (SELECT employee_id FROM employees)
    LOOP
        DBMS_APPLICATION_INFO.SET_MODULE(
            module_name => 'SALARY_UPDATE',
            action_name => 'Processing ' || rec.employee_id
        );
        
        DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS(
            rindex => DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS_NOHINT,
            slno => 0,
            op_name => 'Employee Processing',
            sofar => rec.employee_id,
            totalwork => v_total,
            units => 'employees'
        );
        
        -- 业务逻辑
        UPDATE employees SET salary = salary * 1.05 WHERE employee_id = rec.employee_id;
    END LOOP;
END;
/

-- 监控查询
SELECT sid, serial#, module, action FROM v$session WHERE module = 'SALARY_UPDATE';
SELECT * FROM v$session_longops WHERE opname = 'Employee Processing';

8.3 依赖关系查询

sql 复制代码
-- 查看存储过程依赖的表
SELECT referenced_owner, referenced_name, referenced_type
FROM all_dependencies
WHERE owner = 'HR' 
AND name = 'ADJUST_EMPLOYEE_SALARY'
AND type = 'PROCEDURE'
ORDER BY referenced_type;

-- 查看哪些对象依赖该过程
SELECT name, type
FROM all_dependencies
WHERE referenced_owner = 'HR'
AND referenced_name = 'ADJUST_EMPLOYEE_SALARY';

九、最佳实践与规范

9.1 命名规范

sql 复制代码
-- 前缀规范
- 存储过程:p_业务模块_操作(如 p_emp_update_salary)
- 函数:f_业务模块_计算(如 f_emp_calc_bonus)
- 包:pkg_业务模块(如 pkg_employee_mgmt)
- 参数:p_参数名(输入)、p_参数名_out(输出)、p_参数名_io(输入输出)
- 变量:v_变量名(局部)、g_变量名(全局包变量)

9.2 编码规范

sql 复制代码
-- 1. 总是使用 AUTHID 明确权限
CREATE OR REPLACE PROCEDURE secure_proc(...) IS
    AUTHID CURRENT_USER  -- 调用者权限
IS
BEGIN
    ...
END;
/

-- 2. 参数使用 %TYPE 锚定
CREATE OR REPLACE PROCEDURE update_emp (
    p_emp_id IN employees.employee_id%TYPE,  -- 类型自动同步
    p_salary IN employees.salary%TYPE
) IS ...

-- 3. 使用显式游标而非隐式
-- 错误:隐式游标无法处理 NO_DATA_FOUND
SELECT ... INTO ...;  -- 不推荐

-- 正确:显式游标控制
DECLARE
    CURSOR c_emp IS SELECT ...;
BEGIN
    OPEN c_emp;
    LOOP
        FETCH c_emp INTO ...;
        EXIT WHEN c_emp%NOTFOUND;
    END LOOP;
    CLOSE c_emp;
END;

-- 4. 异常处理精细化
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        -- 处理查询不到数据
    WHEN DUP_VAL_ON_INDEX THEN
        -- 处理唯一键冲突
    WHEN OTHERS THEN
        -- 记录错误日志后重新抛出
        log_error(SQLCODE, SQLERRM);
        RAISE;  -- 重新抛出,让上层调用者处理

9.3 性能黄金法则

批量操作替代逐行处理 (FORALL)

避免游标循环中的 SQL (先 JOIN 再处理)

使用 NOCOPY 减少大集合拷贝

SQL 能做的事不要放在 PL/SQL 中

使用 DBMS_PROFILER 定位性能瓶颈

避免在函数中执行 DML (SQL 调用时会导致上下文切换)

避免过度使用自治事务(破坏事务原子性)


十、总结对比

存储过程 vs 函数终极对比

维度 存储过程 函数
返回值 0 或多个(OUT 参数) 必须 1 个(RETURN)
SQL 调用 ❌ 不可 ✅ 可在 SELECT/ WHERE 中调用
事务控制 ✅ 可 COMMIT/ROLLBACK ❌ 应避免(非确定性)
副作用 ✅ 可修改数据 ⚠️ 应保持纯计算
性能 适合复杂业务逻辑 适合计算和转换
调试 较难(无 RETURN) 较易(可单元测试)
使用场景 批处理、ETL、API 封装 公式、验证、数据转换

选择原则

  • 需要返回多个值 → 存储过程
  • 需要在 SQL 中使用 → 函数
  • 需要修改数据 → 存储过程(函数也可但应避免)
  • 纯计算逻辑 → 函数(保持确定性)

掌握存储过程和函数,是 Oracle 后端开发的核心技能。它们能将业务逻辑下沉到数据库层,提升性能、安全性和可维护性。

相关推荐
while(1){yan}1 天前
Spring事务
java·数据库·spring boot·后端·java-ee·mybatis
盛世宏博北京1 天前
高效环境管控:楼宇机房以太网温湿度精准监测系统方案
开发语言·数据库·php·以太网温湿度变送器
运维行者_1 天前
2026 技术升级,OpManager 新增 AI 网络拓扑与带宽预测功能
运维·网络·数据库·人工智能·安全·web安全·自动化
gfdhy1 天前
【C++实战】多态版商品库存管理系统:从设计到实现,吃透面向对象核心
开发语言·数据库·c++·microsoft·毕业设计·毕设
Elastic 中国社区官方博客1 天前
Elasticsearch:上下文工程 vs. 提示词工程
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
小唐同学爱学习1 天前
如何解决海量数据存储
java·数据库·spring boot·mysql
wWYy.1 天前
详解redis(15):缓存雪崩
数据库·redis·缓存
zzcufo1 天前
多邻国第五阶段第13部分
java·开发语言·数据库
这周也會开心1 天前
Redis相关知识点
数据库·redis·缓存
小白爱运维1 天前
MySQL升级8.0.44后登录报错-系统表不支持'MyISAM'存储引擎
数据库·mysql