1. 存储过程基础入门
第一次接触存储过程时,我把它想象成一个预装好的工具箱。比如你家里有个电钻工具箱,每次要用时直接打开就能用,不需要临时去买零件组装。存储过程也是这样,它把常用的SQL操作"打包"好存在数据库里,随时调用执行。
什么是存储过程?简单说就是预先编写好的一组SQL语句集合,经过编译后存储在数据库中。你可以给它起个名字(比如update_inventory),之后通过这个名字就能执行里面的所有SQL。我刚开始做电商项目时,每天要处理上百个订单状态更新,用存储过程后代码量直接减少了70%。
存储过程的核心优势有三点:
- 减少网络传输:原本需要发送10条SQL现在只需传1条调用命令
- 提升性能:预编译特性让执行速度更快
- 增强安全性:可以屏蔽表结构细节,只暴露必要的参数接口
来看个最简单的创建例子:
CREATE PROCEDURE get_employee(IN emp_id INT) BEGIN SELECT * FROM employees WHERE id = emp_id; END这个存储过程接收一个员工ID参数,返回对应的员工信息。调用时只需要:
CALL get_employee(101);2. 参数设计与变量使用
2.1 参数类型详解
存储过程参数就像函数的参数,但更灵活。主要分三种:
- IN参数:最常用,相当于只读输入
- OUT参数:用于返回值
- INOUT参数:既能输入也能输出
我踩过的坑:曾经误把OUT参数当IN用,结果传入的值总是NULL。后来明白OUT参数在调用前是不接收输入值的,它就是个"空容器"。
看个电商库存更新的例子:
CREATE PROCEDURE update_stock( IN product_id INT, IN reduce_qty INT, OUT new_stock INT ) BEGIN UPDATE products SET stock = stock - reduce_qty WHERE id = product_id; SELECT stock INTO new_stock FROM products WHERE id = product_id; END调用时这样使用:
CALL update_stock(1001, 5, @current_stock); SELECT @current_stock; -- 查看返回的库存量2.2 变量与流程控制
存储过程里可以声明局部变量,就像编程语言中的变量:
DECLARE total_price DECIMAL(10,2); DECLARE customer_level VARCHAR(20) DEFAULT '普通';流程控制主要用IF和CASE语句。比如会员折扣计算:
IF purchase_amount > 1000 THEN SET discount = 0.9; ELSEIF purchase_amount > 500 THEN SET discount = 0.95; ELSE SET discount = 1; END IF;3. 复杂逻辑实现技巧
3.1 循环处理数据
做批量操作时循环特别有用。比如给所有VIP用户发积分:
CREATE PROCEDURE add_vip_points() BEGIN DECLARE done INT DEFAULT FALSE; DECLARE user_id INT; DECLARE cur CURSOR FOR SELECT id FROM users WHERE is_vip = 1; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; OPEN cur; read_loop: LOOP FETCH cur INTO user_id; IF done THEN LEAVE read_loop; END IF; UPDATE accounts SET points = points + 100 WHERE user_id = user_id; END LOOP; CLOSE cur; END3.2 错误处理
好的错误处理能让存储过程更健壮。使用DECLARE HANDLER:
DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN GET DIAGNOSTICS CONDITION 1 @err_no = MYSQL_ERRNO; SELECT CONCAT('错误:', @err_no) AS message; ROLLBACK; END;4. 性能调优实战
4.1 执行计划分析
用EXPLAIN查看存储过程中的SQL执行计划:
EXPLAIN SELECT * FROM orders WHERE user_id = 100;重点关注type列(ALL表示全表扫描)、key列(是否用到索引)
4.2 索引优化
在存储过程中频繁查询的字段要建索引。比如:
-- 在orders表的user_id字段添加索引 CREATE INDEX idx_user ON orders(user_id);4.3 避免全表扫描
我曾优化过一个统计报表的存储过程,把:
SELECT COUNT(*) FROM orders WHERE YEAR(create_time) = 2023;改成:
SELECT COUNT(*) FROM orders WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31';性能提升了20倍,因为后者能利用索引。
5. 安全与维护
5.1 权限控制
给存储过程设置执行权限更安全:
GRANT EXECUTE ON PROCEDURE process_order TO order_manager;5.2 版本管理
建议用命名规范管理存储过程版本:
order_process_v1 order_process_v25.3 日志记录
在关键存储过程中添加日志:
CREATE PROCEDURE payment_process(IN order_id INT) BEGIN INSERT INTO procedure_logs VALUES(NOW(), 'payment_process', order_id); -- 支付逻辑... END6. 真实电商案例
6.1 订单处理流程
完整的订单处理存储过程:
CREATE PROCEDURE process_order( IN order_id INT, OUT status_code INT ) BEGIN DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN SET status_code = 500; ROLLBACK; END; START TRANSACTION; -- 1. 验证库存 SELECT COUNT(*) INTO @out_of_stock FROM order_items oi JOIN products p ON oi.product_id = p.id WHERE oi.order_id = order_id AND oi.quantity > p.stock; IF @out_of_stock > 0 THEN SET status_code = 400; ROLLBACK; RETURN; END IF; -- 2. 扣减库存 UPDATE products p JOIN order_items oi ON p.id = oi.product_id SET p.stock = p.stock - oi.quantity WHERE oi.order_id = order_id; -- 3. 更新订单状态 UPDATE orders SET status = '已支付', pay_time = NOW() WHERE id = order_id; -- 4. 记录日志 INSERT INTO order_logs VALUES(order_id, '订单处理完成', NOW()); COMMIT; SET status_code = 200; END6.2 性能对比测试
在我的测试环境中,使用存储过程处理1000个订单:
- 传统方式:28秒
- 存储过程:3.2秒
- 优化后的存储过程:1.8秒
关键优化点:
- 批量更新代替单条更新
- 添加合适的索引
- 减少不必要的中间结果集
存储过程就像数据库里的瑞士军刀,用得好的话能大幅提升开发效率和系统性能。刚开始可能会觉得语法有些复杂,但坚持用上两三个项目后,你会发现它已经成为不可或缺的利器了。