存储过程这玩意儿,放到“存储对象”系列里往往比视图、序列这些“静态对象”显得更特殊,因为它存的不是数据表结构,而是一段真正能跑起来的逻辑。很多同学在这个进阶阶段第一次被卡住,通常不是因为SQL不会写,而是不知道“要怎么把一段逻辑固化到数据库里”,更不知道“参数、变量、控制语句这些到了存储过程里为什么写法变得那么别扭”。
这篇文章就把“存储过程上”这一关扯清楚,重点讲概念定位、创建姿势、参数方向和基础流程控制,涉及Oracle、MySQL、SQL Server、openGauss等主流数据库的差异也会点一下。文中所有例子都适合你在一台自建测试库上直接跑,尤其适合已经从“单条SQL查询”走到“系统集成开发”的朋友。
1. “存储对象”这个系列,为什么偏偏要把过程单拎出来讲
1.1 存储对象不只有表,还有过程和函数这些“可执行对象”
数据库里说的存储对象,通常包含表、视图、索引、序列、同义词、约束,以及另一大类叫“命名PL/SQL块”或“存储子程序”的东西——存储过程、存储函数、触发器、包。前一类保存的是结构或元数据,后一类保存的是业务逻辑。
很多人刚接触时容易把存储过程当成“一个很长的SQL”,这本质上就理解偏了。存储过程和普通SQL相比,核心差异在于:SQL是描述性的,你想让数据库返回一个结果集,只要写出“要什么”;但存储过程是命令式的,你可以定义变量、做循环、做条件判断、捕获异常,甚至在一个过程里去调用另一个过程。
打个比方:视图是一份可复用的“查询模板”,存储过程则是一个打包好的“业务流程处理机”。它接收你喂进去的参数,内部通过变量、逻辑分支、游标、异常把这些数据处理完,最后要么返回结果集、要么返回输出参数、要么直接改表数据。
1.2 为什么项目一旦复杂起来,你就绕不开过程
我在实际开发里遇到过很多类似需求:批量对账要一次性处理几万条明细,不符合规则的记录要单独落到错误日志表;订单超时状态要每隔几分钟批量刷新;老系统要做数据迁移,但源表字段和新表字段的转换规则有二十多条。这种场景如果用应用层代码写,应用要跟数据库来回交互几万次,慢不说,事务边界也难控制。
把逻辑放进存储过程的最大优势是“逻辑离数据最近”。存储过程在数据库实例内部执行,不需要把每行数据拉到应用服务器再做判断,能显著减少网络开销。而且它是编译后存储在数据库中的对象,第一次执行后执行计划可以复用,比应用层拼一堆SQL再逐条发送要稳定得多。
这里也要说句公道话:不是所有逻辑都适合塞进数据库。计算密集型、需要调用外部API、需要读取配置文件之类的业务,当然更适合留在应用层。存储过程真正擅长的场景是:强事务、批量数据操作、复杂查询加工、需要精细控制执行顺序的数据管道。搞清楚这个边界再决定要不要用过程,比盲目迷信“全放存储过程”或“全不放存储过程”都更理智。
1.3 为什么课程标题叫“进阶-存储对象2-存储过程上”
如果你看系统课程的门纲,一般会由浅入深推进:先讲表和数据操作,再讲查询,再到索引、视图、序列,之后才是存储过程。所以“存储对象2”这个编号表示前面已经讲过了静态对象的基本概念,现在进入动态过程对象。
“存储过程上”则意味着这个主题大概率会分两块讲:第一部分把过程的创建、参数、变量、基础条件分支和调用方式讲透;第二部分会深挖游标、循环嵌套、异常处理、动态SQL和性能优化。我这篇文章就陪你走完“上”这一段,把地基打稳,下半段才能踩得住。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 创建一个存储过程,其实只需要掌握四个位置“填空”
2.1 一个典型的Oracle存储过程骨架
先看一段非常朴素的Oracle存储过程,逻辑是按部门编号返回该部门的员工数量。虽然简单,但骨架非常完整:
oracle复制CREATE OR REPLACE PROCEDURE get_emp_count_by_dept(
p_dept_id IN NUMBER,
p_count OUT NUMBER
) IS
v_extra_name VARCHAR2(100);
BEGIN
-- 业务处理部分,这里写赋值、查询、运算
SELECT COUNT(*)
INTO p_count
FROM employees
WHERE department_id = p_dept_id;
-- 也可以通过变量承接中间值
SELECT department_name
INTO v_extra_name
FROM departments
WHERE department_id = p_dept_id;
DBMS_OUTPUT.PUT_LINE('部门名称:' || v_extra_name || ',人数:' || p_count);
EXCEPTION
WHEN NO_DATA_FOUND THEN
p_count := -1;
END get_emp_count_by_dept;
你不需要背下来,只需要理解它由四段组成:
- 声明段:
IS关键字之后,写局部变量,像v_extra_name; - 执行段:
BEGIN ... END之间,这是过程真正干活的区域; - 异常段:执行段末尾的
EXCEPTION部分,捕获和处理意料之外的错误; - 结束标记:
END后面加过程名,也可以不加,但建议加,多文件编辑时清晰度提升很多。
Oracle存储过程的特点之一是所有语句都以分号 ; 结尾,并且赋值符号是 :=,注意它跟“是否相等”的 = 完全是两码事。很多第一次从Java或C#转过来的同学会写 p_count = -1,这是编译过不去的。
2.2 MySQL和openGauss、SQL Server的创建姿势差异
Oracle用 CREATE OR REPLACE PROCEDURE,MySQL从5.0开始也支持,但语法稍微有点不同。MySQL里如果同时存在同名但有不同参数的过程,允许通过 DROP PROCEDURE IF EXISTS 重建,SQL Server也类似。代码如下:
mysql复制DELIMITER //
CREATE PROCEDURE get_emp_count_by_dept(
IN p_dep
