存储过程命名须带业务前缀和动词,禁用sp_开头;参数全用@前缀且明确类型,必填项不设默认值;返回仅用SELECT统一结构;错误处理必须TRY...CATCH+THROW;多表写入须显式事务。

存储过程命名必须带业务前缀和动词,避免 sp_ 开头
SQL Server 里以 sp_ 开头的存储过程会被引擎优先从系统库查找,哪怕你定义在用户库,也可能触发额外解析开销甚至调用错对象。其他数据库虽无此限制,但统一前缀能立刻区分用途。
实际对接时,前端或中间件靠名字识别能力(比如「查订单」还是「删订单」),光看 GetOrder 不如 order_get_by_id 明确——后者一眼看出是按 ID 查,且归属 order 模块。
-
order_create、user_update_status、payment_refund_apply:小写下划线,动词+名词+限定词 - 禁止
spUpdateUser、proc_GetOrderList这类混用大小写或加冗余前缀的写法 - 同一业务域下,所有存储过程统一用相同前缀(如全部以
order_开头),方便权限批量管控
输入参数全用 @ 前缀 + 明确类型,禁用 NULL 默认值掩盖必填逻辑
接口规范的核心是“调用方能不查文档就懂怎么传”。如果参数默认为 NULL,调用方可能误以为可省略,结果过程内部没做非空判断,直接报错或逻辑跳过。
更麻烦的是,有些 ORM 或 JDBC 驱动对 NULL 参数处理不一致——有的发空字符串,有的发 SQL NULL,有的干脆跳过该参数,导致行为不可控。
- 必填参数:声明为
@order_id BIGINT,不设默认值;由调用方保证传入 - 可选参数:显式写
@status_filter VARCHAR(20) = NULL,并在过程开头用IF @status_filter IS NULL分支处理 - 避免用
GETDATE()这类运行时函数作默认值,会破坏测试可重现性
返回结果只走 SELECT,禁用 PRINT、RAISERROR 干扰数据流
很多老代码喜欢用 PRINT 'Success' 或 RAISERROR 输出状态,但这会让客户端收到多结果集(data + message),尤其 Java 的 ResultSet 或 Python 的 fetchall() 容易只取第一个,把错误信息漏掉或当成数据解析失败。
真正需要反馈执行结果(比如影响行数、生成 ID、错误码),应该统一塞进结果集字段里,而不是混在消息通道中。
- 成功时只返回一张表:
SELECT 1 AS code, 'OK' AS msg, @new_order_id AS data - 失败时也返回同结构:
SELECT -1 AS code, 'Invalid user_id' AS msg, NULL AS data - 绝对不用
SET NOCOUNT ON关闭行数提示——它虽减少网络包,但让调用方无法感知是否真有数据返回
错误处理必须用 TRY...CATCH 封装,且 CATCH 块里只抛出带状态码的 THROW
裸写 IF @@ERROR 0 很难覆盖所有异常场景(比如死锁、超时、权限拒绝),而且分散在各处难以维护。统一用 TRY...CATCH 是底线。
但很多人在 CATCH 里又写 RAISERROR,这会导致原错误号丢失(变成 50000),调用方无法区分是唯一键冲突(2627)还是死锁(1205)。SQL Server 2012+ 支持 THROW,它能原样透传错误号和消息。
-
THROW 50001, 'Order not found', 1:自定义业务错误,客户端可按 50001 特殊处理 -
THROW(无参数):原样重抛上层错误,保留原始错误号与上下文 -
CATCH块末尾必须有RETURN,防止控制流意外继续往下执行
最常被绕过的其实是事务边界——不是所有存储过程都自动套事务,但只要涉及多表写入,就必须显式 BEGIN TRAN + COMMIT/ROLLBACK,且 ROLLBACK 必须放在 CATCH 里。这点一旦漏掉,数据不一致不会立刻报错,而是悄悄发生。










