存储过程是Oracle数据库中使用频率极高的对象,它把一段业务逻辑封装在数据库端,客户端只需要发起一次调用就能完成复杂的数据处理,既减少了网络往返,也便于统一维护。不过对于初学者来说,PLSQL调用存储过程的具体方式、参数怎么传、OUT参数的返回值怎么取,往往容易搞混。本文将从基础调用语法讲起,逐步覆盖参数模式、异常处理、调试技巧以及常见报错的排查方法,并配套示例代码和练习题,帮助读者系统掌握存储过程的调用。

一、存储过程调用的几种基本方式
在PL/SQL环境中调用存储过程,最常见的方式有三种:使用SQL*Plus的EXEC命令、使用SQL语句的CALL关键字,以及在PL/SQL匿名块中直接调用。三者适用场景略有差别,下面分别说明。
EXEC是SQL*Plus和SQL Developer等工具提供的命令,它本质上是对匿名块调用的简化封装,适合调用无返回值或只有OUT参数的过程,写法非常简洁:
-- 假设存在过程 add_emp(p_name IN VARCHAR2, p_salary IN NUMBER)
EXEC add_emp('张三', 8000);
-- 带绑定变量的写法
VARIABLE v_name VARCHAR2(20)
EXEC add_emp(:v_name, 8000);CALL是SQL标准关键字,任何支持SQL调用的环境都能用,比如JDBC、ODBC等。需要注意的是CALL调用带OUT参数的过程时,必须使用绑定变量来接收返回值,而且CALL只能调用无返回值的过程和函数。相比之下,在匿名块中直接调用最为灵活,也是实际开发中用得最多的方式,可以在调用前后编写额外的逻辑:
BEGIN
add_emp('李四', 9500);
DBMS_OUTPUT.PUT_LINE('员工添加成功');
END;
/三种方式各有取舍:EXEC简单快速但依赖客户端工具;CALL通用性强但灵活性一般;匿名块调用功能最完整,适合复杂业务场景。日常开发中建议优先使用匿名块方式,便于后续扩展事务控制或异常处理逻辑。
二、参数模式与传参方式的细节
调用存储过程之前,必须先弄清楚参数模式。PL/SQL过程参数有三种模式:IN表示输入参数,调用时传入值,过程内部不能修改它;OUT表示输出参数,过程执行完毕后会把值返回给调用方;IN OUT则兼具两者特性,既传入初始值又可能被修改后带回。调用带OUT参数的过程时,必须准备变量来接收结果,否则会报编译错误。
传参方式有两种:位置传参和命名传参。位置传参按参数声明顺序依次赋值,简洁直观;命名传参使用=>符号指定参数名与值的对应关系,参数较多时更清晰,而且可以只给有默认值的参数之外的必要参数赋值。两种方式可以混用,但一旦使用了命名传参,后面的参数都必须用命名方式。
-- 先创建一个演示用存储过程
CREATE OR REPLACE PROCEDURE calc_bonus(
p_salary IN NUMBER,
p_rate IN NUMBER DEFAULT 0.1,
p_bonus OUT NUMBER
) AS
BEGIN
p_bonus := p_salary * p_rate;
EXCEPTION
WHEN OTHERS THEN
p_bonus := 0;
END;
/
-- 匿名块中调用,演示三种传参方式
DECLARE
v_bonus NUMBER;
BEGIN
-- 位置传参
calc_bonus(8000, 0.2, v_bonus);
DBMS_OUTPUT.PUT_LINE('位置传参结果: ' || v_bonus);
-- 命名传参,省略有默认值的 p_rate
calc_bonus(p_salary => 8000, p_bonus => v_bonus);
DBMS_OUTPUT.PUT_LINE('命名传参结果: ' || v_bonus);
END;
/需要特别提醒的是,OUT参数在调用时只能传变量,不能传常量或表达式。IN参数传值时要注意类型匹配,虽然PL/SQL会做隐式转换,但依赖隐式转换容易埋下性能和精度隐患,建议显式保证类型一致。
三、异常处理与常见报错排查
调用存储过程时的报错可以分成两类:编译期错误和运行期错误。编译期错误通常在调用语句提交时就暴露,比如过程名拼写错误、参数个数不匹配、缺少执行权限等。遇到ORA-06550错误并提示PLS-00201标识符未定义时,多半是过程不存在或当前用户没有EXECUTE权限,可以让DBA执行授权:
-- 授权语法
GRANT EXECUTE ON hr.add_emp TO scott;
-- 跨用户调用时需要带方案名前缀
BEGIN
hr.add_emp('王五', 7000);
END;
/运行期错误则需要靠异常处理来应对。如果存储过程内部没有处理异常,异常会传播到调用方,所以调用方的匿名块中最好也写上EXCEPTION段。常见的运行期异常包括NO_DATA_FOUND(SELECT INTO未命中数据)、TOO_MANY_ROWS(SELECT INTO返回多行)、DUP_VAL_ON_INDEX(违反唯一约束)等。可以通过SQLERRM和SQLCODE获取错误详情并记录日志:
DECLARE
v_bonus NUMBER;
BEGIN
calc_bonus(8000, 0.2, v_bonus);
DBMS_OUTPUT.PUT_LINE('奖金: ' || v_bonus);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('未找到数据: ' || SQLERRM);
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('错误代码: ' || SQLCODE || ', 信息: ' || SQLERRM);
ROLLBACK;
END;
/调试方面还有一个高频问题:调用了DBMS_OUTPUT.PUT_LINE却看不到输出。原因是服务器输出默认关闭,需要在SQL*Plus中执行SET SERVEROUTPUT ON,在SQL Developer中打开DBMS Output面板并启用连接,输出才会显示出来。另外建议在开发阶段用DBMS_UTILITY.FORMAT_ERROR_BACKTRACE输出出错的具体行号,定位问题效率会高很多。
四、配套练习题推荐
掌握调用语法离不开动手练习,下面几道题目按照难度递进排列,建议在本地Oracle环境中逐一完成。
练习一:创建一个无参数的存储过程show_sysdate,功能是输出当前系统时间,分别用EXEC命令和匿名块两种方式调用它。这道题主要巩固最基本的调用语法。
练习二:创建过程get_emp_info,接收员工编号作为IN参数,通过OUT参数返回员工姓名和工资,在匿名块中调用并把结果打印出来,并对员工不存在的情况做NO_DATA_FOUND异常处理。这道题训练OUT参数接收和异常捕获。
练习三:创建过程transfer_sal,模拟两个员工之间的工资转账,从A员工扣除一定金额加到B员工身上,要求在过程内部使用事务控制,保证扣款和加款要么都成功要么都回滚。这道题涉及IN OUT参数和事务一致性的综合运用,完成后再故意传入不存在的员工编号验证回滚效果。
练习四:给第二题中的get_emp_info增加调用权限控制,让另一个用户只能通过带方案前缀的方式调用它,练习GRANT EXECUTE授权和跨方案调用的写法。
五、调用存储过程的注意事项
实际项目中有几个容易踩坑的点值得强调。第一,存储过程内部如果执行了DML但没有COMMIT,事务会悬挂在会话上,调用方必须明确事务的提交职责,通常建议由最外层调用方统一提交,过程内部不要随意COMMIT或ROLLBACK,否则会破坏调用方的事务边界。
第二,调用存储过程产生的锁不会随调用结束自动释放,只有事务提交或回滚后锁才消失。如果调用后长时间不提交,其他会话操作相关表时会出现锁等待,表现为程序卡住。排查时可以查询v$session和v$lock视图定位阻塞源。
第三,命名传参符号是=>,中间没有空格,写成= >会报语法错误;同样地,过程名和参数名在Oracle中默认不区分大小写,但存入数据字典时会转为大写,如果过程是在双引号下创建的带小写名或特殊字符名,调用时必须严格用双引号包裹原名,否则会报对象不存在的错误。掌握这些细节后,配合前面章节的示例反复练习,调用存储过程就会变得得心应手。