存储过程用于数据修改、事务处理和多结果返回,函数仅限纯读计算且不可修改数据库状态;涉及DML、流程控制、多输出或非确定性操作时必须用存储过程。

要改数据、做事务、返回多个结果,就用存储过程;只计算、不写库、要嵌进SELECT里,就用函数。
需要执行 INSERT/UPDATE/DELETE 时必须用 CREATE PROCEDURE
函数在 MySQL、SQL Server、PostgreSQL 中都明确禁止修改数据库状态。一旦你在 CREATE FUNCTION 里写 UPDATE users SET status = 'active',直接报错:Invalid function definition: Cannot modify a table in a function。而存储过程天然支持 DML,还能配合 BEGIN TRANSACTION 和 ROLLBACK 做一致性保障。
常见错误现象:
- 把批量发优惠券逻辑硬塞进函数,结果编译失败
- 想用函数记录操作日志(
INSERT INTO audit_log),被数据库拦住
实操建议:
- 涉及多表更新、状态流转、定时任务触发的数据变更,一律走
CREATE PROCEDURE - 哪怕只是“查+改”两步,只要含写操作,就不能用函数
- 函数里调用另一个含写操作的存储过程?也不行——函数体内禁止
EXEC或CALL
要在 SELECT 或 WHERE 中动态生成字段值,优先选函数
比如把 user_id 转成脱敏手机号、根据订单金额算税后价、把时间戳转为“3小时前”这类纯读+计算场景,函数能直接当表达式用:SELECT name, dbo.mask_phone(mobile) FROM users。存储过程做不到这点——它不能出现在 SELECT 列表里,也不能在 WHERE 条件中调用。
性能影响要注意:
- 标量函数在大表上被逐行调用,可能拖慢整个查询(尤其没内联优化的数据库)
- MySQL 的函数不支持
GETDATE()这类非确定性函数,否则创建失败 - SQL Server 中,函数不能用
TRY...CATCH,出错只能靠上层捕获
实操建议:
- 确认逻辑只读、无副作用,再封装成函数
- 高频调用的计算逻辑,优先用内置函数(如
ROUND()、CONCAT()),比自定义函数更稳 - 需要返回多列或多行?别硬撑——改用表值函数(
RETURNS TABLE)或直接用存储过程 +SELECT输出结果集
需要输出多个值或控制流程分支,只能选存储过程
函数只允许一个 RETURN,且类型固定(标量或表)。但业务中常要同时返回新插入的 ID、处理条数、错误码等——这只能靠存储过程的 OUT 参数实现:CREATE PROCEDURE sp_create_order @cust_id INT, @order_id INT OUTPUT, @affected_rows INT OUTPUT。另外,IF、WHILE、游标、临时表这些流程控制能力,函数基本不支持(表值函数除外)。
容易踩的坑:
- 误以为函数能用
DECLARE @temp TABLE—— 标量函数里连表变量都不让建 - 想在函数里判断条件后返回不同格式字符串,结果发现分支逻辑写不下去
- 把本该用
OUT参数返回的状态码,硬塞进字符串拼接里传出来,后续解析困难
实操建议:
- 接口需返回成功/失败+详情信息时,用存储过程 + 多个
OUT参数比拼 JSON 字符串更可靠 - 复杂 ETL 步骤(查源→清洗→校验→落库→发通知)必须用存储过程,函数撑不住
- 注意 SQL Server 中,函数不能调用
GETDATE(),但存储过程可以——时间敏感逻辑别放错地方
最常被忽略的一点:函数在查询计划中可能被多次求值,尤其是没标记为 DETERMINISTIC 时;而存储过程的执行边界清晰,调试和监控都更容易定位。选型不是看“哪个更高级”,而是看“哪条路不会在半道被数据库拦下来”。

















