📊 SQL 入门 Day 23:存储过程与函数

存储过程和函数是 SQL 中最强大的可编程特性------把业务逻辑封装在数据库层,减少网络往返,提升执行效率。理解它们能帮你从"写 SQL 的人"进化成"设计数据库系统的人"。


场景引入

想象你在做一个电商系统,用户下单后需要:

  1. 检查库存是否充足
  2. 扣减库存
  3. 创建订单
  4. 生成支付记录

如果每次都在应用代码里一步步执行,至少需要 4 次网络往返,而且事务边界很难保证一致性。存储过程可以一次性搞定。


一、存储过程(Stored Procedure)

1. 创建存储过程

sql 复制代码
DELIMITER $$

CREATE PROCEDURE sp_create_order(
    IN  p_user_id   INT,
    IN  p_product_id INT,
    IN  p_quantity  INT,
    OUT p_order_id  INT,
    OUT p_message   VARCHAR(100)
)
BEGIN
    DECLARE v_stock INT;

    -- 开启事务
    START TRANSACTION;

    -- 检查库存
    SELECT stock INTO v_stock
    FROM products
    WHERE id = p_product_id
    FOR UPDATE;

    IF v_stock >= p_quantity THEN
        -- 扣减库存
        UPDATE products
        SET stock = stock - p_quantity
        WHERE id = p_product_id;

        -- 创建订单
        INSERT INTO orders (user_id, product_id, quantity, status)
        VALUES (p_user_id, p_product_id, p_quantity, 'pending');

        SET p_order_id = 921714;
        SET p_message = '下单成功';

        COMMIT;
    ELSE
        SET p_order_id = 0;
        SET p_message = '库存不足';
        ROLLBACK;
    END IF;
END$$

DELIMITER ;

2. 调用存储过程

sql 复制代码
-- 调用
CALL sp_create_order(1001, 5, 2, @order_id, @msg);

-- 查看输出参数
SELECT @order_id, @msg;

执行结果:

@order_id @msg
1024 下单成功

3. 删除存储过程

sql 复制代码
DROP PROCEDURE IF EXISTS sp_create_order;

4. 查看存储过程定义

sql 复制代码
SHOW CREATE PROCEDURE sp_create_order;

-- 或从系统表查询
SELECT * FROM information_schema.ROUTINES
WHERE ROUTINE_NAME = 'sp_create_order';

二、存储函数(Stored Function)

函数与存储过程的关键区别:函数必须有返回值,可以在 SQL 表达式中直接使用。

1. 创建函数

sql 复制代码
DELIMITER $$

CREATE FUNCTION fn_get_user_order_count(p_user_id INT)
RETURNS INT
DETERMINISTIC
READS SQL DATA
BEGIN
    DECLARE v_count INT;

    SELECT COUNT(*) INTO v_count
    FROM orders
    WHERE user_id = p_user_id;

    RETURN v_count;
END$$

DELIMITER ;

2. 使用函数

sql 复制代码
-- 在 SELECT 中直接调用
SELECT id, name,
       fn_get_user_order_count(id) AS order_count
FROM users
WHERE id = 1001;

执行结果:

id name order_count
1001 张三 12

3. 函数特性标识说明

标识 含义
DETERMINISTIC 相同输入总是返回相同结果(影响查询缓存)
NOT DETERMINISTIC 默认,输入相同结果可能不同(如 NOW()
READS SQL DATA 函数包含 SELECT 操作
MODIFIES SQL DATA 函数包含 INSERT/UPDATE/DELETE

三、存储过程 vs 存储函数

对比维度 存储过程 存储函数
返回值 通过 OUT 参数,可返回多个值 必须有且仅有一个返回值
调用方式 CALL proc(...) 在 SQL 表达式中直接使用
事务能力 支持事务控制(COMMIT/ROLLBACK) 通常不支持事务操作
SELECT 中调用 不可以 可以
常见场景 批量数据处理、事务操作 计算逻辑、数据校验

四、进阶技巧

1. 游标(Cursor)------逐行处理数据

sql 复制代码
DELIMITER $$

CREATE PROCEDURE sp_generate_monthly_report()
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE v_user_id INT;
    DECLARE v_user_name VARCHAR(50);

    -- 定义游标
    DECLARE cur CURSOR FOR
        SELECT id, name FROM users WHERE status = 'active';

    -- 定义 NOT FOUND 处理
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

    -- 创建临时表存结果
    CREATE TEMPORARY TABLE IF NOT EXISTS tmp_report (
        user_id INT,
        user_name VARCHAR(50),
        order_total DECIMAL(10,2)
    );

    OPEN cur;

    read_loop: LOOP
        FETCH cur INTO v_user_id, v_user_name;
        IF done THEN
            LEAVE read_loop;
        END IF;

        INSERT INTO tmp_report
        SELECT v_user_id, v_user_name, COALESCE(SUM(amount), 0)
        FROM orders
        WHERE user_id = v_user_id
          AND order_date >= DATE_FORMAT(CURDATE(), '%Y-%m-01');
    END LOOP;

    CLOSE cur;

    SELECT * FROM tmp_report;
    DROP TEMPORARY TABLE tmp_report;
END$$

DELIMITER ;

2. 条件处理(Handler)

sql 复制代码
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
BEGIN
    ROLLBACK;
    SELECT '发生异常,事务已回滚' AS error_msg;
END;

DECLARE EXIT HANDLER FOR SQLSTATE '23000'
BEGIN
    SELECT '违反唯一约束' AS error_msg;
END;
Handler 类型 行为
CONTINUE 执行处理后继续执行后续语句
EXIT 执行处理后退出当前 BEGIN...END 块

3. 变量作用域

sql 复制代码
CREATE PROCEDURE sp_demo()
BEGIN
    DECLARE v_msg VARCHAR(20) DEFAULT '外层';

    BEGIN
        DECLARE v_msg VARCHAR(20) DEFAULT '内层';  -- 同名局部变量,不影响外层
        SET v_msg = '修改内层';
    END;

    SELECT v_msg;  -- 结果:'外层'
END;

五、实战:自动归档旧订单

sql 复制代码
DELIMITER $$

CREATE PROCEDURE sp_archive_orders(p_days INT)
BEGIN
    DECLARE v_count INT DEFAULT 0;
    DECLARE v_archived INT DEFAULT 0;

    -- 计算归档日期
    SET @cutoff_date = DATE_SUB(CURDATE(), INTERVAL p_days DAY);

    -- 检查待归档数据
    SELECT COUNT(*) INTO v_count
    FROM orders
    WHERE status = 'completed'
      AND order_date < @cutoff_date;

    IF v_count = 0 THEN
        SELECT CONCAT('没有需要归档的数据') AS result;
    ELSE
        -- 插入归档表
        INSERT INTO orders_archive
        SELECT * FROM orders
        WHERE status = 'completed'
          AND order_date < @cutoff_date;

        SET v_archived = ROW_COUNT();

        -- 删除原表数据
        DELETE FROM orders
        WHERE status = 'completed'
          AND order_date < @cutoff_date;

        SELECT CONCAT('已归档 ', v_archived, ' 条订单记录') AS result;
    END IF;
END$$

DELIMITER ;

-- 执行:归档 90 天前的已完成订单
CALL sp_archive_orders(90);

执行结果:

sql 复制代码
+-----------------------------------+
| result                            |
+-----------------------------------+
| 已归档 156 条订单记录              |
+-----------------------------------+

六、注意事项

  1. 安全性 :存储过程默认以调用者权限执行,但通过 SQL SECURITY DEFINER 可以提升权限------谨慎使用
  2. 调试困难:相比应用代码,存储过程的调试手段有限,复杂逻辑建议在应用层实现
  3. 版本控制:存储过程存在数据库内,建议在项目中使用迁移脚本(Flyway/Liquibase)管理
  4. 性能陷阱:不恰当的使用游标逐行处理性能极差,能用集合操作解决的不要用游标
  5. MySQL 差异:MySQL 的存储过程语法与其他数据库(PostgreSQL/PGPLSQL、SQL Server/T-SQL)差异较大,迁移时注意

总结

  • 存储过程:适用于事务性批量操作,一次调用完成多步 DML
  • 存储函数:适用于可复用的计算逻辑,在 SELECT 中直接调用
  • 游标:逐行处理数据的利器,但能用集合操作就别用游标
  • 最佳实践:简单逻辑用存储函数,复杂事务用存储过程,业务规则放在应用层

存储过程和函数是把"数据库当计算引擎用"的核心工具。理解它们,你对 SQL 的掌控力会上一个大台阶。

相关推荐
勤奋的树懒5 小时前
从手写 SQL 到 Windows 工具:致远 OA 文件清理实践
数据库·sql·windows server·致远oa
vx_Biye_Design6 小时前
springboot游泳馆系统93765-计算机课程设计、毕业设计
java·javascript·spring boot·后端·python·spring·课程设计
名字还没想好☜6 小时前
Java NIO ByteBuffer 实战:flip/clear/compact 三个绕晕人的方法与 position/limit 心智模型
java·开发语言·后端·spring·nio
专业程序开发源6 小时前
springbootLivehouse票务系统-计算机课程设计、毕业设计
vue.js·spring boot·后端·python·django·课程设计·pygame
PC2005_cloud6 小时前
Rust学习笔记:所有权、借用与生命周期——Rust的核心机制
前端·后端
蜗牛互联网6 小时前
语音AI开始边听边说,改变的不只是响应速度
java·人工智能·后端·语音识别
Mav6 小时前
[个人学习记录]从零构建高性能 LLM 推理网关:Go 语言 SSE 流式转发、级联取消与背压
后端
狼爷7 小时前
磁盘IO打满怎么办?我用5个真实案例,总结了这套可复用的排查方法论
后端·性能优化
小羊在睡觉7 小时前
HLS视频
linux·后端·golang
苍何7 小时前
小米版 Codex,干活有点猛啊
后端