写在前面

欢迎来到MySQL系列教学第11天!今天我们将学习MySQL的存储过程(Stored Procedure)函数(Function)。这是MySQL提供的在数据库服务器端封装业务逻辑的机制,可以帮助我们更有效地组织和复用SQL代码。

无论你是刚接触数据库编程的新手,还是准备面试的求职者,这篇文章都将帮助你全面掌握MySQL存储过程和函数的核心知识。


在这里插入图片描述

一、什么是存储过程

1.1 存储过程的定义

存储过程是一组预编译的SQL语句集合,存储在数据库服务器中,可以被应用程序调用执行。

-- 简单的存储过程示例
DELIMITER $$

CREATE PROCEDURE GetUserById(IN userId INT)
BEGIN
    SELECT * FROM users WHERE id = userId;
END$$

DELIMITER ;

-- 调用存储过程
CALL GetUserById(1);

1.2 存储过程的优点

优点 说明
性能优势 预编译执行,减少SQL解析和网络传输开销
代码复用 业务逻辑封装在数据库中,多处调用
安全性 可以限制用户直接访问表,只能通过存储过程操作
减少网络流量 多条SQL只需一次调用

1.3 存储过程的缺点

缺点 说明
可维护性差 调试困难,版本控制不便
移植性差 不同数据库的存储过程语法差异大
扩展性受限 难以应对复杂业务逻辑
增加数据库负担 复杂计算应该在应用层完成

1.4 存储过程 vs 应用程序代码

对比项 存储过程 应用程序代码
性能 高(预编译) 中(需要编译/解释)
可维护性
调试难度
版本控制 不便 方便(Git等)
复杂业务 不适合 适合
移植性

经验之谈

  • 简单、高频、性能敏感的操作可以考虑存储过程
  • 复杂业务逻辑应该放在应用层
  • 互联网公司大多不推荐大量使用存储过程

二、创建存储过程

2.1 基本语法

DELIMITER $$

CREATE PROCEDURE procedure_name(
    [IN | OUT | INOUT] param1 datatype,
    [IN | OUT | INOUT] param2 datatype,
    ...
)
BEGIN
    -- SQL语句
END$$

DELIMITER ;

DELIMITER说明

  • MySQL默认使用;作为语句结束符
  • 存储过程中包含多条SQL语句,需要临时修改结束符
  • 通常使用$$//作为临时结束符

2.2 参数模式

模式 说明 用途
IN 输入参数(默认) 向存储过程传递值
OUT 输出参数 从存储过程返回值
INOUT 输入输出参数 既可传入又可返回
DELIMITER $$

-- IN参数示例
CREATE PROCEDURE GetUsersByStatus(IN p_status INT)
BEGIN
    SELECT * FROM users WHERE status = p_status;
END$$

-- OUT参数示例
CREATE PROCEDURE GetUserCount(OUT p_count INT)
BEGIN
    SELECT COUNT(*) INTO p_count FROM users;
END$$

-- INOUT参数示例
CREATE PROCEDURE DoubleValue(INOUT p_value INT)
BEGIN
    SET p_value = p_value * 2;
END$$

DELIMITER ;

-- 调用示例
CALL GetUsersByStatus(1);

SET @user_count = 0;
CALL GetUserCount(@user_count);
SELECT @user_count;

SET @num = 5;
CALL DoubleValue(@num);
SELECT @num;  -- 结果:10

2.3 完整的存储过程示例

DELIMITER $$

CREATE PROCEDURE CreateOrder(
    IN p_user_id BIGINT,
    IN p_product_id BIGINT,
    IN p_quantity INT,
    OUT p_order_id BIGINT,
    OUT p_result VARCHAR(100)
)
BEGIN
    DECLARE v_price DECIMAL(10,2);
    DECLARE v_stock INT;
    DECLARE v_total DECIMAL(12,2);
    
    -- 声明异常处理
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        SET p_result = '订单创建失败';
        RESIGNAL;
    END;
    
    -- 开启事务
    START TRANSACTION;
    
    -- 查询商品信息
    SELECT price, stock INTO v_price, v_stock 
    FROM products 
    WHERE id = p_product_id FOR UPDATE;
    
    -- 检查库存
    IF v_stock < p_quantity THEN
        ROLLBACK;
        SET p_result = '库存不足';
        SET p_order_id = -1;
    ELSE
        -- 计算总价
        SET v_total = v_price * p_quantity;
        
        -- 扣减库存
        UPDATE products 
        SET stock = stock - p_quantity 
        WHERE id = p_product_id;
        
        -- 创建订单
        INSERT INTO orders (user_id, total_amount, status, created_at)
        VALUES (p_user_id, v_total, 1, NOW());
        
        SET p_order_id = LAST_INSERT_ID();
        SET p_result = '订单创建成功';
        
        COMMIT;
    END IF;
END$$

DELIMITER ;

三、流程控制语句

3.1 IF条件语句

DELIMITER $$

CREATE PROCEDURE CheckUserAge(IN p_age INT)
BEGIN
    IF p_age < 0 THEN
        SELECT '年龄不能为负数' AS message;
    ELSEIF p_age < 18 THEN
        SELECT '未成年' AS message;
    ELSEIF p_age < 60 THEN
        SELECT '成年人' AS message;
    ELSE
        SELECT '老年人' AS message;
    END IF;
END$$

DELIMITER ;

CALL CheckUserAge(25);  -- 成年人

3.2 CASE语句

DELIMITER $$

CREATE PROCEDURE GetOrderStatusText(IN p_status INT)
BEGIN
    CASE p_status
        WHEN 0 THEN SELECT '待支付' AS status_text;
        WHEN 1 THEN SELECT '已支付' AS status_text;
        WHEN 2 THEN SELECT '已发货' AS status_text;
        WHEN 3 THEN SELECT '已完成' AS status_text;
        WHEN 4 THEN SELECT '已取消' AS status_text;
        ELSE SELECT '未知状态' AS status_text;
    END CASE;
END$$

DELIMITER ;

3.3 WHILE循环

DELIMITER $$

CREATE PROCEDURE CalculateSum(IN p_n INT, OUT p_sum INT)
BEGIN
    DECLARE i INT DEFAULT 1;
    DECLARE s INT DEFAULT 0;
    
    WHILE i <= p_n DO
        SET s = s + i;
        SET i = i + 1;
    END WHILE;
    
    SET p_sum = s;
END$$

DELIMITER ;

SET @result = 0;
CALL CalculateSum(100, @result);
SELECT @result;  -- 结果:5050

3.4 REPEAT循环

DELIMITER $$

CREATE PROCEDURE CalculateSumRepeat(IN p_n INT, OUT p_sum INT)
BEGIN
    DECLARE i INT DEFAULT 1;
    DECLARE s INT DEFAULT 0;
    
    REPEAT
        SET s = s + i;
        SET i = i + 1;
    UNTIL i > p_n END REPEAT;
    
    SET p_sum = s;
END$$

DELIMITER ;

3.5 LOOP循环

DELIMITER $$

CREATE PROCEDURE CalculateSumLoop(IN p_n INT, OUT p_sum INT)
BEGIN
    DECLARE i INT DEFAULT 1;
    DECLARE s INT DEFAULT 0;
    
    sum_loop: LOOP
        IF i > p_n THEN
            LEAVE sum_loop;
        END IF;
        SET s = s + i;
        SET i = i + 1;
    END LOOP sum_loop;
    
    SET p_sum = s;
END$$

DELIMITER ;

3.6 循环控制语句

语句 作用
LEAVE 跳出循环(类似break)
ITERATE 跳过当前迭代,继续下一次(类似continue)
DELIMITER $$

CREATE PROCEDURE PrintOddNumbers(IN p_max INT)
BEGIN
    DECLARE i INT DEFAULT 0;
    
    num_loop: LOOP
        SET i = i + 1;
        
        IF i > p_max THEN
            LEAVE num_loop;
        END IF;
        
        IF i % 2 = 0 THEN
            ITERATE num_loop;  -- 跳过偶数
        END IF;
        
        SELECT i AS odd_number;
    END LOOP num_loop;
END$$

DELIMITER ;

四、游标(CURSOR)

4.1 什么是游标

游标是一种数据结构,用于遍历查询结果集中的每一行记录。

4.2 游标的使用步骤

DELIMITER $$

CREATE PROCEDURE ProcessAllUsers()
BEGIN
    -- 1. 声明变量
    DECLARE done INT DEFAULT FALSE;
    DECLARE v_id INT;
    DECLARE v_name VARCHAR(50);
    DECLARE v_email VARCHAR(100);
    
    -- 2. 声明游标
    DECLARE user_cursor CURSOR FOR 
        SELECT id, name, email FROM users WHERE status = 1;
    
    -- 3. 声明继续处理程序
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    
    -- 4. 打开游标
    OPEN user_cursor;
    
    -- 5. 遍历游标
    read_loop: LOOP
        FETCH user_cursor INTO v_id, v_name, v_email;
        
        IF done THEN
            LEAVE read_loop;
        END IF;
        
        -- 处理每一行数据
        INSERT INTO user_logs (user_id, action, created_at)
        VALUES (v_id, CONCAT('Processed user: ', v_name), NOW());
        
    END LOOP;
    
    -- 6. 关闭游标
    CLOSE user_cursor;
END$$

DELIMITER ;

4.3 游标注意事项

  1. 游标必须在声明变量之后、声明处理程序之前声明
  2. 游标使用完必须关闭
  3. 游标会占用服务器资源,大数据量时考虑分批处理
-- 批量处理示例
DELIMITER $$

CREATE PROCEDURE BatchProcessUsers(IN p_batch_size INT)
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE v_id INT;
    DECLARE v_count INT DEFAULT 0;
    
    DECLARE user_cursor CURSOR FOR 
        SELECT id FROM users WHERE processed = 0 LIMIT p_batch_size;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    
    batch_loop: LOOP
        SET done = FALSE;
        OPEN user_cursor;
        
        read_loop: LOOP
            FETCH user_cursor INTO v_id;
            IF done THEN
                LEAVE read_loop;
            END IF;
            
            -- 处理记录
            UPDATE users SET processed = 1 WHERE id = v_id;
            SET v_count = v_count + 1;
        END LOOP;
        
        CLOSE user_cursor;
        
        -- 如果没有处理任何记录,退出
        IF v_count = 0 THEN
            LEAVE batch_loop;
        END IF;
        
        -- 重置计数器,继续下一批
        SET v_count = 0;
    END LOOP batch_loop;
END$$

DELIMITER ;

五、自定义函数

5.1 函数与存储过程的区别

特性 函数(FUNCTION) 存储过程(PROCEDURE)
返回值 必须返回一个值 可以返回多个值(OUT参数)或不返回
调用方式 在SQL语句中调用 使用CALL调用
参数 只有IN参数 IN、OUT、INOUT参数
使用场景 计算、转换 复杂业务逻辑

5.2 创建函数

DELIMITER $$

-- 简单函数:计算年龄
CREATE FUNCTION CalculateAge(birth_date DATE) RETURNS INT
DETERMINISTIC  -- 确定性函数(相同输入总是相同输出)
BEGIN
    RETURN TIMESTAMPDIFF(YEAR, birth_date, CURDATE());
END$$

-- 在查询中使用函数
SELECT name, CalculateAge(birth_date) AS age FROM users;

DELIMITER ;

5.3 函数的确定性

特性 说明
DETERMINISTIC 确定性函数,相同输入总是产生相同输出
NOT DETERMINISTIC 非确定性函数(默认),如NOW()、RAND()
DELIMITER $$

-- 非确定性函数
CREATE FUNCTION GetRandomNumber() RETURNS INT
NOT DETERMINISTIC
BEGIN
    RETURN FLOOR(RAND() * 100);
END$$

-- 确定性函数
CREATE FUNCTION AddNumbers(a INT, b INT) RETURNS INT
DETERMINISTIC
BEGIN
    RETURN a + b;
END$$

DELIMITER ;

5.4 复杂函数示例

DELIMITER $$

-- 计算订单折扣价
CREATE FUNCTION CalculateDiscountPrice(
    p_original_price DECIMAL(10,2),
    p_user_level INT
) RETURNS DECIMAL(10,2)
DETERMINISTIC
BEGIN
    DECLARE v_discount_rate DECIMAL(3,2);
    
    CASE p_user_level
        WHEN 1 THEN SET v_discount_rate = 0.95;  -- 普通会员95折
        WHEN 2 THEN SET v_discount_rate = 0.90;  -- 银卡会员9折
        WHEN 3 THEN SET v_discount_rate = 0.85;  -- 金卡会员85折
        WHEN 4 THEN SET v_discount_rate = 0.80;  -- 钻石会员8折
        ELSE SET v_discount_rate = 1.00;          -- 无折扣
    END CASE;
    
    RETURN p_original_price * v_discount_rate;
END$$

DELIMITER ;

-- 使用函数
SELECT 
    product_name,
    original_price,
    CalculateDiscountPrice(original_price, user_level) AS discount_price
FROM products p
JOIN users u ON u.id = @current_user_id;

六、存储过程和函数的区别

6.1 详细对比

对比项 存储过程 函数
语法 CREATE PROCEDURE CREATE FUNCTION
返回值 通过OUT参数返回,可多个 通过RETURN返回,只有一个
调用方式 CALL procedure_name() 在SQL中直接使用 function_name()
参数类型 IN、OUT、INOUT 只有IN
事务控制 可以包含事务 不能包含事务
使用场景 复杂业务逻辑 简单计算、转换
性能 适合批量操作 适合单行计算

6.2 选择建议

场景 推荐
需要返回多个值 存储过程
需要在SQL中嵌套使用 函数
复杂业务逻辑 存储过程
简单计算(如格式化) 函数
需要事务控制 存储过程

七、触发器(TRIGGER)

7.1 什么是触发器

触发器是一种特殊的存储过程,在特定事件(INSERT、UPDATE、DELETE)发生时自动执行。

7.2 触发器的类型

触发时机 说明
BEFORE 在操作执行前触发
AFTER 在操作执行后触发
触发事件 说明
INSERT 插入数据时触发
UPDATE 更新数据时触发
DELETE 删除数据时触发

7.3 创建触发器

-- 创建审计日志表
CREATE TABLE user_audit_log (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT,
    action VARCHAR(20),
    old_value TEXT,
    new_value TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 创建INSERT触发器
DELIMITER $$

CREATE TRIGGER trg_user_insert
AFTER INSERT ON users
FOR EACH ROW
BEGIN
    INSERT INTO user_audit_log (user_id, action, new_value)
    VALUES (NEW.id, 'INSERT', CONCAT('name:', NEW.name, ', email:', NEW.email));
END$$

-- 创建UPDATE触发器
CREATE TRIGGER trg_user_update
AFTER UPDATE ON users
FOR EACH ROW
BEGIN
    INSERT INTO user_audit_log (user_id, action, old_value, new_value)
    VALUES (
        NEW.id, 
        'UPDATE', 
        CONCAT('name:', OLD.name, ', email:', OLD.email),
        CONCAT('name:', NEW.name, ', email:', NEW.email)
    );
END$$

-- 创建DELETE触发器
CREATE TRIGGER trg_user_delete
BEFORE DELETE ON users
FOR EACH ROW
BEGIN
    INSERT INTO user_audit_log (user_id, action, old_value)
    VALUES (OLD.id, 'DELETE', CONCAT('name:', OLD.name, ', email:', OLD.email));
END$$

DELIMITER ;

7.4 NEW和OLD关键字

触发事件 NEW OLD
INSERT 新插入的行 NULL
UPDATE 更新后的行 更新前的行
DELETE NULL 被删除的行

7.5 查看和删除触发器

-- 查看所有触发器
SHOW TRIGGERS;

-- 查看特定表的触发器
SHOW TRIGGERS FROM database_name WHERE `table` = 'users';

-- 删除触发器
DROP TRIGGER IF EXISTS trg_user_insert;

八、实战:订单状态变更日志记录

8.1 业务需求

记录订单状态的所有变更历史,包括:

  • 变更时间
  • 原状态
  • 新状态
  • 操作人
  • 备注

8.2 表结构设计

-- 订单表
CREATE TABLE orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_no VARCHAR(32) UNIQUE NOT NULL,
    user_id BIGINT NOT NULL,
    status TINYINT NOT NULL DEFAULT 0,
    total_amount DECIMAL(12,2) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- 订单状态历史表
CREATE TABLE order_status_history (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_id BIGINT NOT NULL,
    old_status TINYINT,
    new_status TINYINT NOT NULL,
    operator_id BIGINT,
    remark VARCHAR(500),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 状态字典表
CREATE TABLE status_dict (
    status_code TINYINT PRIMARY KEY,
    status_name VARCHAR(50) NOT NULL
);

INSERT INTO status_dict VALUES 
(0, '待支付'),
(1, '已支付'),
(2, '已发货'),
(3, '已完成'),
(4, '已取消');

8.3 创建触发器记录状态变更

DELIMITER $$

CREATE TRIGGER trg_order_status_change
AFTER UPDATE ON orders
FOR EACH ROW
BEGIN
    -- 只有状态变更时才记录
    IF OLD.status != NEW.status THEN
        INSERT INTO order_status_history (
            order_id, 
            old_status, 
            new_status, 
            operator_id,
            remark
        ) VALUES (
            NEW.id,
            OLD.status,
            NEW.status,
            @current_operator_id,  -- 通过会话变量传递
            CONCAT('订单状态从 ', 
                   (SELECT status_name FROM status_dict WHERE status_code = OLD.status),
                   ' 变更为 ',
                   (SELECT status_name FROM status_dict WHERE status_code = NEW.status))
        );
    END IF;
END$$

DELIMITER ;

8.4 存储过程:更新订单状态

DELIMITER $$

CREATE PROCEDURE UpdateOrderStatus(
    IN p_order_id BIGINT,
    IN p_new_status TINYINT,
    IN p_operator_id BIGINT,
    IN p_remark VARCHAR(500),
    OUT p_result VARCHAR(100)
)
BEGIN
    DECLARE v_current_status TINYINT;
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        SET p_result = '操作失败';
        RESIGNAL;
    END;
    
    -- 查询当前状态
    SELECT status INTO v_current_status 
    FROM orders 
    WHERE id = p_order_id;
    
    -- 检查订单是否存在
    IF v_current_status IS NULL THEN
        SET p_result = '订单不存在';
    -- 检查状态是否合法
    ELSEIF v_current_status = p_new_status THEN
        SET p_result = '新状态与当前状态相同';
    -- 检查状态流转是否合法
    ELSEIF NOT IsValidStatusTransition(v_current_status, p_new_status) THEN
        SET p_result = '非法的状态流转';
    ELSE
        -- 设置操作人(用于触发器)
        SET @current_operator_id = p_operator_id;
        
        -- 更新状态
        UPDATE orders 
        SET status = p_new_status,
            updated_at = NOW()
        WHERE id = p_order_id;
        
        SET p_result = '状态更新成功';
    END IF;
END$$

-- 辅助函数:检查状态流转是否合法
CREATE FUNCTION IsValidStatusTransition(
    p_old_status TINYINT,
    p_new_status TINYINT
) RETURNS BOOLEAN
DETERMINISTIC
BEGIN
    -- 定义合法的状态流转
    -- 0(待支付) -> 1(已支付) 或 4(已取消)
    -- 1(已支付) -> 2(已发货)
    -- 2(已发货) -> 3(已完成)
    
    RETURN (
        (p_old_status = 0 AND p_new_status IN (1, 4)) OR
        (p_old_status = 1 AND p_new_status = 2) OR
        (p_old_status = 2 AND p_new_status = 3)
    );
END$$

DELIMITER ;

8.5 查询订单状态历史

-- 创建视图方便查询
CREATE VIEW v_order_status_history AS
SELECT 
    h.id,
    h.order_id,
    o.order_no,
    s1.status_name AS old_status_name,
    s2.status_name AS new_status_name,
    h.operator_id,
    h.remark,
    h.created_at
FROM order_status_history h
JOIN orders o ON h.order_id = o.id
LEFT JOIN status_dict s1 ON h.old_status = s1.status_code
JOIN status_dict s2 ON h.new_status = s2.status_code;

-- 查询某订单的状态历史
SELECT * FROM v_order_status_history WHERE order_id = 1 ORDER BY created_at;

九、踩坑提醒

9.1 存储过程调试困难

问题

  • MySQL存储过程没有内置调试器
  • 错误信息不够详细

解决方案

DELIMITER $$

CREATE PROCEDURE DebugExample()
BEGIN
    DECLARE v_step INT DEFAULT 0;
    
    -- 使用日志表记录执行步骤
    INSERT INTO debug_log (message) VALUES ('Step 1: Start');
    
    SET v_step = 1;
    -- 业务逻辑...
    
    INSERT INTO debug_log (message) VALUES ('Step 2: After operation 1');
    
    SET v_step = 2;
    -- 业务逻辑...
    
    INSERT INTO debug_log (message, data) 
    VALUES ('Step 3: Variable value', CONCAT('v_step=', v_step));
    
END$$

DELIMITER ;

9.2 业务逻辑不要放在数据库层

反模式

-- 不好的做法:在存储过程中实现复杂业务逻辑
CREATE PROCEDURE ComplexBusinessLogic()
BEGIN
    -- 调用外部API
    -- 复杂的条件判断
    -- 大量的数据处理
    -- ...
END;

推荐做法

  • 存储过程只处理数据操作
  • 复杂业务逻辑放在应用层
  • 使用ORM框架管理数据访问

9.3 触发器的陷阱

1. 递归触发

-- 危险:触发器中修改同一张表会导致递归
CREATE TRIGGER trg_bad_trigger
AFTER UPDATE ON users
FOR EACH ROW
BEGIN
    UPDATE users SET updated_at = NOW() WHERE id = NEW.id;  -- 递归!
END;

2. 性能问题

-- 触发器中的操作会延长原操作的时间
-- 大批量操作时,触发器会严重影响性能

-- 解决方案:批量操作时临时禁用触发器
-- (生产环境慎用)

3. 隐式行为

-- 触发器是"隐藏"的逻辑,可能导致意外行为
-- 建议:
-- 1. 文档化所有触发器
-- 2. 命名规范:trg_表名_操作
-- 3. 定期审查触发器的必要性

9.4 存储过程的版本控制

问题:存储过程存储在数据库中,不方便版本控制

解决方案

  1. 将所有存储过程定义保存在Git仓库中
  2. 使用迁移工具管理存储过程变更
  3. 命名规范包含版本信息
-- 文件命名:sp_create_order_v1.sql, sp_create_order_v2.sql
-- 存储过程命名:CreateOrder, CreateOrderV2

十、面试高频考点

Q1:存储过程的优缺点是什么?

  • 优点
    1. 预编译执行,性能好
    2. 减少网络传输
    3. 代码复用
    4. 安全性高(可以限制直接访问表)
  • 缺点
    1. 调试困难
    2. 可移植性差
    3. 版本控制不便
    4. 增加数据库负担

Q2:存储过程和函数有什么区别?

  1. 存储过程用CALL调用,函数在SQL中直接调用
  2. 存储过程可以返回多个值(OUT参数),函数只能返回一个值
  3. 存储过程可以有IN/OUT/INOUT参数,函数只有IN参数
  4. 存储过程可以包含事务控制,函数不能
  5. 函数可以用在SELECT语句中,存储过程不能

Q3:什么时候应该使用存储过程?

  1. 需要封装复杂的数据操作逻辑
  2. 多个应用需要共享相同的业务逻辑
  3. 对性能要求极高,需要减少网络往返
  4. 需要限制用户直接访问表,只能通过存储过程操作

不应该使用的情况

  1. 复杂业务逻辑(应该在应用层)
  2. 需要频繁变更的逻辑
  3. 需要跨数据库移植的系统

Q4:触发器有哪些使用场景?

  1. 审计日志(记录数据变更历史)
  2. 数据同步(一个表变更同步到另一个表)
  3. 数据校验(复杂的数据完整性检查)
  4. 级联操作(替代外键的级联删除/更新)

注意事项

  • 避免递归触发
  • 注意性能影响
  • 文档化触发器逻辑

Q5:游标的使用场景和注意事项?

  • 使用场景:需要逐行处理查询结果时
  • 注意事项
    1. 游标会占用服务器资源,大数据量时考虑分批处理
    2. 游标必须在声明变量之后、处理程序之前声明
    3. 使用完必须关闭游标
    4. 能用集合操作(UPDATE/INSERT…SELECT)解决的,不要用游标

十一、总结

今天我们深入学习了MySQL的存储过程和函数:

  1. 存储过程:预编译的SQL语句集合,有优缺点
  2. 参数模式:IN、OUT、INOUT三种参数类型
  3. 流程控制:IF、CASE、WHILE、REPEAT、LOOP
  4. 游标:遍历查询结果集的机制
  5. 函数:返回单个值的存储程序,可在SQL中调用
  6. 触发器:自动响应数据变更事件的机制
  7. 实战应用:订单状态变更日志记录系统

核心要点

  • 存储过程适合封装数据操作逻辑,不适合复杂业务
  • 理解存储过程和函数的区别,选择合适的工具
  • 谨慎使用触发器,注意性能和递归问题
  • 业务逻辑优先放在应用层

下一步预告

Day12我们将学习MySQL的视图(View),探讨如何通过视图简化复杂查询、实现数据安全访问,以及视图的使用场景和性能考虑。敬请期待!


参考资料

MySQL官方文档 - 触发器


互动话题

  1. 你们项目中使用了存储过程吗?主要用在什么场景?
  2. 对于"业务逻辑不应该放在数据库层"这个观点,你怎么看?
  3. 你遇到过触发器导致的性能问题吗?是如何解决的?

欢迎在评论区分享你的经验和见解!如果觉得本文有帮助,别忘了点赞收藏哦~

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐