MySQL 存储过程完整完美示例(覆盖基础、入参、出参、循环、判断、事务、异常捕获)

前置说明

    1. 环境:MySQL 5.7 / 8.0 通用;
    1. 示例业务:用户订单统计业务,包含:输入用户ID、输出订单总数、遍历订单、条件判断、事务回滚、异常捕获;
    1. 语法关键点:DELIMITER 分隔符修改、IN/OUT/INOUT 参数、DECLARE 变量、HANDLER 异常、LOOP/IFCOMMIT/ROLLBACK

一、先建测试表(配套示例使用)

复制代码
-- 用户表
CREATE TABLE `user` (
  id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
  username VARCHAR(50) NOT NULL COMMENT '用户名',
  create_time DATETIME DEFAULT NOW()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 订单表
CREATE TABLE `order` (
  order_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '订单号',
  user_id INT NOT NULL COMMENT '所属用户',
  order_money DECIMAL(10,2) NOT NULL COMMENT '订单金额',
  order_status TINYINT NOT NULL COMMENT '0待支付 1已完成 2已取消',
  create_time DATETIME DEFAULT NOW(),
  FOREIGN KEY (user_id) REFERENCES `user`(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 测试数据
INSERT INTO `user`(username) VALUES ('张三'),('李四');
INSERT INTO `order`(user_id,order_money,order_status)
VALUES
(1,99.00,1),
(1,199.50,0),
(2,59.90,1);

二、示例1:基础存储过程(IN入参 + OUT出参,统计用户完成订单总额)

功能:传入用户ID,输出该用户已完成订单总金额

复制代码
-- 修改语句结束符为 $$,避免 ; 提前终止存储过程定义
DELIMITER $$

-- 创建存储过程
CREATE PROCEDURE GetUserFinishOrderTotal(
    IN p_user_id INT,        -- IN:输入参数,用户ID
    OUT p_total_money DECIMAL(10,2)  -- OUT:输出参数,总金额
)
BEGIN
    -- 查询已完成订单 status=1,求和赋值给输出参数
    SELECT IFNULL(SUM(order_money), 0.00)
    INTO p_total_money
    FROM `order`
    WHERE user_id = p_user_id AND order_status = 1;
END $$

-- 恢复默认结束符 ;
DELIMITER ;

-- ==================== 调用测试 ====================
CALL GetUserFinishOrderTotal(1, @total);
SELECT @total AS 张三已完成订单总额; -- 结果 99.00

CALL GetUserFinishOrderTotal(2, @total2);
SELECT @total2 AS 李四已完成订单总额; -- 结果 59.90

三、示例2:带 IF 判断、循环、INOUT 双向参数

功能:批量更新用户所有未支付订单为取消,返回修改条数

复制代码
DELIMITER $$
CREATE PROCEDURE CancelAllUnPayOrder(
    IN p_user_id INT,
    INOUT p_update_count INT -- INOUT:可传值、可输出结果
)
BEGIN
    -- 定义局部变量
    DECLARE v_has_data INT DEFAULT 1;
    DECLARE v_order_id INT;

    -- 定义游标:读取该用户所有待支付订单
    DECLARE order_cursor CURSOR FOR
        SELECT order_id FROM `order` WHERE user_id = p_user_id AND order_status = 0;

    -- 游标遍历完毕时,修改标记
    DECLARE EXIT HANDLER FOR NOT FOUND SET v_has_data = 0;

    -- 初始化修改计数
    SET p_update_count = 0;

    -- 打开游标
    OPEN order_cursor;

    -- 循环读取
    read_loop: LOOP
        -- 读取一行数据到变量
        FETCH order_cursor INTO v_order_id;

        -- 无数据则跳出循环
        IF v_has_data = 0 THEN
            LEAVE read_loop;
        END IF;

        -- 更新订单状态为取消 2
        UPDATE `order` SET order_status = 2 WHERE order_id = v_order_id;
        SET p_update_count = p_update_count + 1;
    END LOOP read_loop;

    -- 关闭游标
    CLOSE order_cursor;
END $$
DELIMITER ;

-- ==================== 调用测试 ====================
SET @count = 0;
CALL CancelAllUnPayOrder(1, @count);
SELECT @count AS 取消订单数量; -- 用户1原本1条待支付,返回1

四、示例3:带事务 + 异常回滚(生产级标准模板)

业务:新增用户同时创建一笔初始化订单,任一SQL失败全部回滚

复制代码
DELIMITER $$
CREATE PROCEDURE AddUserWithInitOrder(
    IN p_username VARCHAR(50),
    IN p_init_money DECIMAL(10,2),
    OUT p_new_user_id INT,
    OUT p_result_msg VARCHAR(100)
)
BEGIN
    -- 声明异常:任意SQL报错触发回滚
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK; -- 出错回滚
        SET p_result_msg = '执行失败:事务已回滚';
    END;

    -- 开启事务
    START TRANSACTION;

    -- 1.插入用户
    INSERT INTO `user`(username) VALUES (p_username);
    SET p_new_user_id = LAST_INSERT_ID();

    -- 2.插入初始化订单
    INSERT INTO `order`(user_id, order_money, order_status)
    VALUES (p_new_user_id, p_init_money, 0);

    -- 无异常则提交
    COMMIT;
    SET p_result_msg = '执行成功:用户与初始化订单创建完成';
END $$
DELIMITER ;

-- ==================== 调用测试 ====================
CALL AddUserWithInitOrder('王五', 29.90, @uid, @msg);
SELECT @uid AS 新用户ID, @msg AS 执行结果;

五、常用管理存储过程语句

复制代码
-- 1.查看数据库所有存储过程
SHOW PROCEDURE STATUS WHERE Db = '你的库名';

-- 2.查看存储过程源码
SHOW CREATE PROCEDURE GetUserFinishOrderTotal;

-- 3.删除存储过程(存在才删,避免报错)
DROP PROCEDURE IF EXISTS GetUserFinishOrderTotal;

-- 4.查询存储过程参数信息
SELECT * FROM information_schema.PARAMETERS WHERE SPECIFIC_NAME='GetUserFinishOrderTotal';

六、关键语法说明(避坑重点)

    1. DELIMITER $$
      MySQL 默认以 ; 为执行结束符,存储过程内部大量分号会导致提前截断定义,所以临时修改结束符,定义完成后恢复 ;
    1. 三种参数区别
    • IN:只读入,过程内修改不影响外部变量;
    • OUT:仅向外输出,传入值会被清空;
    • INOUT:可读可写,双向传递。
    1. DECLARE 局部变量
      必须写在 BEGIN 最开头,游标、异常处理器也要放在最前,顺序:变量 → 游标 → 异常。
    1. 事务+异常组合
      生产环境增删改多表操作必须加 DECLARE EXIT HANDLER FOR SQLEXCEPTION,保证原子性。
    1. 表名反引号 order
      order 是MySQL关键字,作为表名必须用反引号包裹,否则语法报错。

七、生产使用建议

    1. 复杂业务拆分为多个存储过程,避免单段代码过长;
    1. 禁止在存储过程中做大量大表循环,会锁表影响性能;
    1. 存储过程内尽量少写业务逻辑,复杂计算放应用层;
    1. 定期备份存储过程源码,不要只依赖数据库内定义。