{T}

存储过程

存储过程(Stored Procedure)是预编译并存储在数据库中的一组 SQL 语句,像一个"数据库端的函数",可以被反复调用。它能封装复杂业务逻辑、提升性能(预编译)、降低网络开销。本文以 MySQL 语法为主,兼顾通用标准。

为什么需要存储过程

优势说明
减少网络往返多条 SQL 打包成一次调用
预编译优化首次编译后复用执行计划
封装复用复杂逻辑集中管理
权限控制只暴露存储过程,不开放表级权限
业务一致性事务在存储过程内保证

劣势:与代码逻辑耦合、跨库迁移成本高、调试困难。所以互联网应用常把复杂逻辑放应用层,存储过程多用于数据完整性约束、批量任务、报表等场景。

创建存储过程

基本语法

sql
CREATE PROCEDURE 过程名 (参数列表)
BEGIN
    过程体(SQL 语句 + 流程控制)
END

DELIMITER 说明

MySQL 默认用 ; 作为语句结束符,但过程体内部也有 ;,所以创建前临时把结束符改成 //

sql
DELIMITER //

CREATE PROCEDURE sp_hello()
BEGIN
    SELECT 'Hello, World';
END //

DELIMITER ;

完整示例

sql
-- 查询所有英雄
DELIMITER //
CREATE PROCEDURE sp_get_all_heros()
BEGIN
    SELECT id, name, hp_max FROM heros;
END //
DELIMITER ;

调用与删除存储过程

sql
-- 调用
CALL sp_get_all_heros();

-- 查看已定义的过程
SHOW PROCEDURE STATUS WHERE Db = '数据库名';

-- 查看过程定义
SHOW CREATE PROCEDURE sp_get_all_heros;

-- 删除
DROP PROCEDURE IF EXISTS sp_get_all_heros;

参数:IN / OUT / INOUT

参数类型方向说明
IN(默认)传入调用时传入,过程内不能改回
OUT传出过程把结果赋给它,调用方接收
INOUT双向既传值又取值
sql
-- 根据定位统计英雄数量,通过 OUT 返回
DELIMITER //
CREATE PROCEDURE sp_count_by_role(
    IN p_role VARCHAR(20),
    OUT p_count INT
)
BEGIN
    SELECT COUNT(*) INTO p_count
    FROM heros WHERE role_main = p_role;
END //
DELIMITER ;

-- 调用
CALL sp_count_by_role('战士', @cnt);
SELECT @cnt;   -- 取出 OUT 参数值

INOUT 示例

sql
DELIMITER //
CREATE PROCEDURE sp_add(INOUT p_num INT)
BEGIN
    SET p_num = p_num + 10;
END //
DELIMITER ;

SET @n = 5;
CALL sp_add(@n);
SELECT @n;   -- 15

变量

局部变量(DECLARE)

在过程内用 DECLARE 声明,SET/SELECT ... INTO 赋值:

sql
DELIMITER //
CREATE PROCEDURE sp_calc()
BEGIN
    DECLARE v_total INT DEFAULT 0;     -- 声明局部变量,默认 0
    DECLARE v_avg DECIMAL(10,2);

    SELECT SUM(hp_max), AVG(hp_max)
      INTO v_total, v_avg               -- INTO 赋值
    FROM heros;

    SELECT v_total AS total_hp, v_avg AS avg_hp;
END //
DELIMITER ;

用户变量(@)

@ 开头,作用于整个会话,不依赖存储过程:

sql
SET @x = 10;
SELECT @x;

流程控制

IF ... ELSE

sql
DELIMITER //
CREATE PROCEDURE sp_check(IN p_hp INT)
BEGIN
    IF p_hp > 7000 THEN
        SELECT '高血量';
    ELSEIF p_hp > 5000 THEN
        SELECT '中血量';
    ELSE
        SELECT '低血量';
    END IF;
END //
DELIMITER ;

CASE

sql
DELIMITER //
CREATE PROCEDURE sp_role_type(IN p_role VARCHAR(20))
BEGIN
    CASE p_role
        WHEN '法师' THEN SELECT '远程/法术';
        WHEN '射手' THEN SELECT '远程/物理';
        WHEN '坦克' THEN SELECT '前排/肉盾';
        ELSE SELECT '其他';
    END CASE;
END //
DELIMITER ;

WHILE 循环

sql
-- 循环插入数字
DELIMITER //
CREATE PROCEDURE sp_fill(IN p_n INT)
BEGIN
    DECLARE i INT DEFAULT 1;
    WHILE i <= p_n DO
        INSERT INTO numbers (val) VALUES (i);
        SET i = i + 1;
    END WHILE;
END //
DELIMITER ;

还有 REPEAT ... UNTILLOOP 循环,可根据条件选择。

游标(CURSOR)

游标用于逐行遍历查询结果集。存储过程里处理多行数据时使用:

sql
DELIMITER //
CREATE PROCEDURE sp_iterate()
BEGIN
    DECLARE v_name VARCHAR(50);
    DECLARE done INT DEFAULT 0;
    -- 声明游标:绑定查询
    DECLARE cur CURSOR FOR SELECT name FROM heros WHERE hp_max > 6000;
    -- 声明 NOT FOUND 处理器:遍历完置 done=1
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

    OPEN cur;
    loop_label: LOOP
        FETCH cur INTO v_name;          -- 取一行
        IF done THEN
            LEAVE loop_label;           -- 遍历完退出
        END IF;
        SELECT v_name;                   -- 处理该行
    END LOOP;
    CLOSE cur;
END //
DELIMITER ;

游标步骤DECLARE(声明)→ OPEN(打开)→ FETCH(取行)→ CLOSE(关闭)。必须声明 NOT FOUND 处理器,否则遍历到最后会报错。

错误处理(条件处理器)

DECLARE ... HANDLER 捕获异常,避免事务中断后无人回滚:

sql
DELIMITER //
CREATE PROCEDURE sp_transfer(
    IN p_from INT, IN p_to INT, IN p_amt DECIMAL(10,2)
)
BEGIN
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;                        -- 出错回滚
        SELECT '转账失败';
    END;

    START TRANSACTION;
    UPDATE accounts SET balance = balance - p_amt WHERE id = p_from;
    UPDATE accounts SET balance = balance + p_amt WHERE id = p_to;
    COMMIT;
    SELECT '转账成功';
END //
DELIMITER ;
处理器触发时机
CONTINUE HANDLER出错后继续执行
EXIT HANDLER出错后终止过程

存储过程 vs 函数 vs 触发器

特性存储过程函数触发器
返回值可无(OUT 参数)必须有返回
调用方式CALL表达式/SELECT事件自动触发
能否被 SELECT 用不能不能
事务支持有限支持
用途复杂业务/批量计算返回单值数据变更后自动处理

存储过程最佳实践

  1. 命名规范sp_ 前缀 + 动词(sp_get_xxx);
  2. 参数校验:入口校验输入合法性,避免非法数据进入;
  3. 结合事务:多步写操作包 START TRANSACTION,配错误处理器回滚;
  4. 控制复杂度:过长的存储过程难以维护,拆分成多个;
  5. 注意迁移成本:存储过程依赖特定数据库方言,跨库(MySQL→PG/Oracle)需重写;
  6. 应用层优先:互联网场景优先把业务逻辑放应用层,存储过程用于强约束与批处理。

总结

语法速查

操作语法
创建CREATE PROCEDURE 名(参数) ... BEGIN ... END
调用CALL 名(参数)
删除DROP PROCEDURE 名
局部变量DECLARE v INT DEFAULT 0
赋值SET v = ... / SELECT ... INTO v
条件IF / ELSEIF / ELSECASE
循环WHILE / REPEAT / LOOP
游标DECLARE/OPEN/FETCH/CLOSE
异常DECLARE EXIT HANDLER FOR SQLEXCEPTION

关键点

  1. 存储过程 = 预编译的 SQL 集合,封装复杂逻辑、减少网络往返;
  2. 参数 IN(入)/OUT(出)/INOUT(双向)控制数据方向;
  3. 流程控制(IF/CASE/循环)+ 变量 + 游标 + 错误处理器构成过程语言能力;
  4. MySQL 需用 DELIMITER 修改结束符以创建多语句过程;
  5. 互联网场景权衡:复杂逻辑优先应用层,存储过程用于事务强约束、批处理、报表。