1. MySQL存储过程概述
存储过程是MySQL数据库中一组预编译的SQL语句集合,它像脚本一样存储在数据库中,可以被应用程序多次调用。我第一次接触存储过程是在处理一个电商平台的订单统计需求时,当时需要每天凌晨计算前一天的销售数据,存储过程帮我解决了定时任务和复杂计算的难题。
存储过程的主要特点包括:
- 预编译执行:存储过程在首次创建时进行编译,后续调用直接执行编译后的代码
- 减少网络传输:客户端只需传递存储过程名和参数,避免发送大量SQL语句
- 提高安全性:通过权限控制可以限制对底层表的直接访问
- 代码复用:一次创建多次调用,减少代码冗余
2. 存储过程创建与基础语法
2.1 创建存储过程的基本结构
创建存储过程使用CREATE PROCEDURE语句,基本语法如下:
DELIMITER // CREATE PROCEDURE 过程名([参数列表]) BEGIN -- 存储过程体 -- 包含SQL语句和流程控制 END // DELIMITER ;这里有几个关键点需要注意:
- DELIMITER用于临时修改语句分隔符,因为存储过程体内包含分号
- 参数列表可以包含IN(输入)、OUT(输出)和INOUT(输入输出)三种类型参数
- BEGIN和END之间是存储过程的主体部分
2.2 存储过程参数详解
存储过程支持三种参数类型:
- IN参数:输入参数,调用者传入值给存储过程
CREATE PROCEDURE GetEmployee(IN emp_id INT) BEGIN SELECT * FROM employees WHERE id = emp_id; END- OUT参数:输出参数,存储过程通过它返回值给调用者
CREATE PROCEDURE GetCount(OUT total INT) BEGIN SELECT COUNT(*) INTO total FROM products; END- INOUT参数:既是输入也是输出参数
CREATE PROCEDURE IncreaseSalary(INOUT amount DECIMAL(10,2)) BEGIN SET amount = amount * 1.1; -- 涨薪10% END3. 存储过程变量与流程控制
3.1 变量声明与使用
在存储过程中可以使用变量来存储中间结果:
DECLARE var_name datatype [DEFAULT value];例如:
CREATE PROCEDURE CalculateOrder(IN order_id INT) BEGIN DECLARE subtotal DECIMAL(10,2) DEFAULT 0; DECLARE tax DECIMAL(10,2); DECLARE total DECIMAL(10,2); -- 计算逻辑 SELECT SUM(price*quantity) INTO subtotal FROM order_items WHERE order_id = order_id; SET tax = subtotal * 0.1; SET total = subtotal + tax; -- 更新订单总金额 UPDATE orders SET amount = total WHERE id = order_id; END3.2 流程控制语句
存储过程支持丰富的流程控制:
- IF-THEN-ELSE语句:
IF condition THEN statements; ELSEIF condition THEN statements; ELSE statements; END IF;- CASE语句:
CASE case_value WHEN value THEN statements; [WHEN value THEN statements; ...] [ELSE statements;] END CASE;- 循环语句:
- WHILE循环:
WHILE condition DO statements; END WHILE;- REPEAT循环:
REPEAT statements; UNTIL condition END REPEAT;- LOOP循环:
[begin_label:] LOOP statements; IF condition THEN LEAVE begin_label; END IF; END LOOP begin_label;4. 存储过程错误处理
4.1 声明处理程序
MySQL允许为特定错误条件声明处理程序:
DECLARE handler_type HANDLER FOR condition_value [, condition_value] ... handler_statementshandler_type可以是:
- CONTINUE:继续执行后续语句
- EXIT:退出当前BEGIN...END块
condition_value可以是:
- SQLSTATE值
- MySQL错误代码
- SQLWARNING
- NOT FOUND
- SQLEXCEPTION
示例:
CREATE PROCEDURE SafeDelete(IN user_id INT) BEGIN DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; SELECT 'Error occurred, transaction rolled back' AS message; END; START TRANSACTION; DELETE FROM user_logs WHERE user_id = user_id; DELETE FROM users WHERE id = user_id; COMMIT; SELECT 'User deleted successfully' AS message; END4.2 使用SIGNAL抛出错误
可以在存储过程中主动抛出错误:
CREATE PROCEDURE CheckInventory(IN product_id INT, IN qty INT) BEGIN DECLARE stock INT; SELECT quantity INTO stock FROM inventory WHERE product_id = product_id; IF stock < qty THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Insufficient inventory'; END IF; -- 正常处理逻辑 END5. 存储过程最佳实践与优化
5.1 性能优化技巧
- 避免在循环中执行SQL:将数据先查询到临时表或变量中
- 合理使用临时表:复杂计算可以分步使用临时表
- 使用游标处理结果集:
DECLARE cur CURSOR FOR SELECT id, name FROM products; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; OPEN cur; read_loop: LOOP FETCH cur INTO pid, pname; IF done THEN LEAVE read_loop; END IF; -- 处理逻辑 END LOOP; CLOSE cur;- 使用PREPARE执行动态SQL:
SET @sql = CONCAT('SELECT * FROM ', table_name); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;5.2 安全与维护建议
- 使用DEFINER和SQL SECURITY控制执行权限
CREATE DEFINER='admin'@'localhost' PROCEDURE SecureProc() SQL SECURITY DEFINER BEGIN -- 以定义者权限执行 END- 添加充分的注释:
CREATE PROCEDURE ComplexCalculation() COMMENT '用于每月财务结算的复杂计算过程' BEGIN -- 过程体 END- 定期检查和优化存储过程:
-- 查看存储过程状态 SHOW PROCEDURE STATUS; -- 查看创建语句 SHOW CREATE PROCEDURE proc_name;6. 实际应用案例
6.1 分页查询存储过程
CREATE PROCEDURE GetPagedProducts( IN page_num INT, IN page_size INT, OUT total_records INT ) BEGIN -- 计算总记录数 SELECT COUNT(*) INTO total_records FROM products; -- 分页查询 SET @offset = (page_num - 1) * page_size; SET @sql = CONCAT('SELECT * FROM products LIMIT ', @offset, ',', page_size); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END6.2 数据迁移存储过程
CREATE PROCEDURE MigrateOldData(IN batch_size INT) BEGIN DECLARE done INT DEFAULT FALSE; DECLARE start_id INT DEFAULT 0; DECLARE max_id INT; -- 获取最大ID SELECT MAX(id) INTO max_id FROM old_table; WHILE start_id <= max_id DO -- 批量迁移数据 INSERT INTO new_table(col1, col2) SELECT col1, col2 FROM old_table WHERE id > start_id AND id <= start_id + batch_size; -- 更新起始ID SET start_id = start_id + batch_size; -- 提交事务 COMMIT; END WHILE; END存储过程是MySQL中强大的功能,合理使用可以显著提高数据库应用的性能和安全性。在实际项目中,我通常会为复杂的业务逻辑创建存储过程,并通过应用程序调用它们。记住要平衡使用存储过程和应用程序代码,避免将过多业务逻辑放入数据库中。