1. 存储过程是什么?为什么需要它?
第一次接触数据库开发时,我总在Java代码里写满SQL语句。每次统计用户订单都要重复写相同的联表查询,直到同事提醒:"你这代码像复读机啊!"后来才知道,数据库里有种叫存储过程的黑科技,能把复杂SQL操作打包成可重复调用的功能模块。
简单来说,存储过程就像是预装在数据库里的APP。比如电商系统要计算会员积分,原本需要在Java/Python里写十几行代码与数据库交互,现在只需调用一句CALL calc_points(用户ID)。我做过测试:一个包含5表关联的统计查询,用存储过程比应用程序处理快3倍,因为减少了网络传输和SQL解析开销。
2. 创建你的第一个存储过程
2.1 基础创建语法
先看个供应商管理的例子。假设有供应商表S和供货记录表SPJ,现在要给S表增加供应总量统计列:
ALTER TABLE s ADD sqty INT DEFAULT 0;接下来创建存储过程自动更新这个统计值:
DELIMITER // CREATE PROCEDURE update_supply_total() BEGIN UPDATE s SET sqty = (SELECT SUM(qty) FROM spj WHERE spj.sno = s.sno); END // DELIMITER ;这里有个坑要注意:MySQL默认用分号结束语句,但存储过程体内也有分号。所以先用DELIMITER //临时修改结束符,就像告诉数据库:"看到//再执行这段代码"。
2.2 执行存储过程
调用存储过程比点外卖还简单:
CALL update_supply_total();执行后查看S表,所有供应商的sqty列都会自动更新。我在项目中用这招处理月度报表生成,原本需要15分钟的统计任务现在3分钟搞定。
3. 带参数的存储过程实战
3.1 输入参数(IN)的使用
查询特定供应商的供货总量时,可以用IN参数动态传入供应商编号:
CREATE PROCEDURE get_supply_qty(IN supplier_id CHAR(2)) BEGIN SELECT SUM(qty) FROM spj WHERE sno = supplier_id; END调用时指定参数值:
CALL get_supply_qty('S1');3.2 输出参数(OUT)的妙用
需要获取计算结果给其他SQL使用时,OUT参数就派上用场了:
CREATE PROCEDURE get_supply_qty_out( IN supplier_id CHAR(2), OUT total_qty INT ) BEGIN SELECT SUM(qty) INTO total_qty FROM spj WHERE sno = supplier_id; END调用方式很特别:
CALL get_supply_qty_out('S1', @result); SELECT @result; -- 这里显示输出值3.3 输入输出参数(INOUT)
最灵活的是INOUT参数,既能输入又能输出。比如实现计数器:
CREATE PROCEDURE counter(INOUT num INT, IN increment INT) BEGIN SET num = num + increment; END使用时需要先设置变量:
SET @count = 1; CALL counter(@count, 5); -- 结果@count变为64. 存储过程进阶技巧
4.1 条件判断与错误处理
在新增供应商时加入存在性检查:
CREATE PROCEDURE add_supplier( IN p_sno CHAR(2), IN p_name VARCHAR(10), IN p_status INT, IN p_city VARCHAR(10) ) BEGIN DECLARE exist_count INT; SELECT COUNT(*) INTO exist_count FROM s WHERE sno = p_sno; IF exist_count > 0 THEN SELECT '供应商已存在!' AS message; ELSE INSERT INTO s VALUES(p_sno, p_name, p_status, p_city); END IF; END4.2 使用游标处理多行数据
当需要逐行处理数据时,游标是唯一选择。比如批量修改项目信息:
CREATE PROCEDURE update_projects() BEGIN DECLARE done INT DEFAULT FALSE; DECLARE j_id CHAR(2); DECLARE cur CURSOR FOR SELECT jno FROM j; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; OPEN cur; read_loop: LOOP FETCH cur INTO j_id; IF done THEN LEAVE read_loop; END IF; UPDATE j SET city = '北京' WHERE jno = j_id; END LOOP; CLOSE cur; END5. 存储过程的管理与优化
5.1 查看和修改存储过程
查看所有存储过程:
SHOW PROCEDURE STATUS;查看具体定义:
SHOW CREATE PROCEDURE update_supply_total;修改存储过程需要先删除再重建,没有ALTER PROCEDURE语法:
DROP PROCEDURE IF EXISTS update_supply_total; CREATE PROCEDURE update_supply_total() ...5.2 性能优化建议
使用COMMENT添加说明:三个月后你肯定记不清这个存储过程是干嘛的
CREATE PROCEDURE calc_stats() COMMENT '用于月度统计报表' BEGIN -- 过程体 END避免过度使用游标:我曾在处理10万条数据时,用游标花了2小时,改成批量UPDATE后只要3分钟
定期维护:存储过程会缓存执行计划,当表结构变化后记得重新编译
CALL update_supply_total(); -- 首次执行会编译权限控制:只给必要用户EXECUTE权限
GRANT EXECUTE ON PROCEDURE db.update_supply_total TO 'report_user';
6. 真实项目经验分享
去年做物流系统时,我设计了一个智能调度存储过程。它接收区域ID和时间范围参数,自动完成以下操作:
- 检查该区域可用车辆
- 计算每个司机的累计工时
- 根据货物类型匹配合适车型
- 生成最优分配方案
原本需要200行Java代码的逻辑,现在只需30行SQL存储过程。最惊喜的是执行时间从45秒降到5秒,因为所有计算都在数据库内部完成,避免了数千次网络往返。
有个坑要提醒:存储过程调试比较麻烦。我的经验是:
- 先用SELECT输出中间结果
- 复杂逻辑拆分成小过程
- 在测试环境充分验证
存储过程就像数据库里的瑞士军刀,用好了能大幅提升效率。但也要避免滥用,对于简单的CRUD操作,直接写SQL可能更清晰。