MySQL存储过程不能用RETURN返回值,必须用OUT/INOUT参数;JDBC需注册OUT参数,MyBatis需配置mode="OUT";聚合函数结果为NULL时须用COALESCE兜底;SQL Server参数需设默认值或校验NULL。

存储过程没设 RETURN,调用端就拿不到值
MySQL 存储过程默认不返回值,RETURN 只在函数(FUNCTION)里合法;过程(PROCEDURE)本身不能用 RETURN 返回标量。你看到“返回空”,大概率是误用了函数写法,或混淆了输出参数与返回值。
正确做法是用 OUT 或 INOUT 参数传递结果:
- 定义过程时声明
OUT result INT - 在过程体内用
SELECT COUNT(*) INTO result FROM ...或直接赋值SET result = 123 - 调用时必须传入用户变量:
CALL proc_name(@id, @out_result); SELECT @out_result;
如果硬要用函数替代,就得改写为 CREATE FUNCTION,且函数体里必须有 RETURN 语句——但函数不能含 UPDATE/INSERT 等修改语句,适用场景受限。
Java 调用存储过程后取不到 OUT 参数值
MyBatis 或 JDBC 直接调用 CALL 时,常因忽略注册 OUT 参数导致变量始终为 null。这不是 SQL 错误,而是驱动层没绑定。
以 JDBC 为例,关键步骤缺一不可:
- 用
CallableStatement而非PreparedStatement -
registerOutParameter(2, Types.INTEGER)必须在execute()前调用,参数索引从 1 开始 - 执行后通过
getInt(2)或getObject(2)获取,不能靠ResultSet
MyBatis 中需显式配置 mode="OUT" 和 jdbcType=INTEGER,例如:
<parameterMap id="procParam"> <parameter property="userId" jdbcType="INTEGER" mode="IN"/> <parameter property="count" jdbcType="INTEGER" mode="OUT"/> </parameterMap>
SQL 层聚合结果为 NULL,上层映射成 int 就崩
这是最隐蔽也最常踩的坑:不是存储过程出错,而是 SUM()、MAX()、COUNT() 在无匹配行时返回 NULL,而 Java 的 int 类型无法接收 null,MyBatis 直接抛 NullPointerException。
解决必须在 SQL 里堵死,不能依赖 Java 层判空:
- 统一用
COALESCE(SUM(...), 0)—— ANSI 标准,跨库安全 - 避免
IFNULL()(MySQL 专属)或ISNULL()(SQL Server 专属),否则换库就挂 - 子查询嵌套时,
COALESCE必须包整个子查询:COALESCE((SELECT SUM(qty) FROM t WHERE id = ?), 0),而不是包里面
注意:COUNT(*) 永远不返回 NULL,但 COUNT(字段) 会忽略 NULL 值,结果仍是数字;真正危险的是 SUM/AVG/MAX/MIN 这类聚合函数。
SQL Server 存储过程参数未传或为 NULL,过程直接中断
SQL Server 对 NULL 参数更敏感:若过程定义中某参数没设默认值,而调用时传了 NULL 或根本没传,就会报 “参数为空” 错误,甚至不进过程体。
防御性写法有两条路:
- 定义参数时加默认值:
@user_id INT = 0或@name NVARCHAR(50) = N'' - 过程开头立刻校验:
IF @user_id IS NULL THROW 50000, 'user_id cannot be null', 1;
特别注意:即使参数带默认值,调用端显式传 NULL 仍会覆盖默认值——所以业务代码里要主动过滤掉无效 NULL,别全甩给 DB 层兜底。
真正麻烦的从来不是 NULL 本身,而是它在 SQL 层、驱动层、ORM 层、Java 类型系统之间反复折射后产生的语义偏差。每个环节都得明确自己该吃掉哪个 NULL,不该把责任推给下一层。

















