存储过程命名须带业务前缀和动词,禁用sp_开头;参数统一加@in/@out/@is前缀且默认null;禁用动态sql;返回一律用select;错误用throw;单过程只做一件事。

存储过程命名必须带业务前缀和动词,别用 sp_ 开头
SQL Server 会优先在 master 数据库里找 sp_ 开头的存储过程,哪怕你明确指定了数据库名,也可能触发额外解析开销甚至误调用系统过程。更实际的问题是:团队里没人能一眼看出这个过程属于订单模块还是用户模块。
实操建议:
- 用业务域+动作命名,比如
orders_create_from_cart、users_merge_duplicate_profiles - 所有参数加统一前缀,如
@in_user_id(输入)、@out_order_id(输出)、@is_debug(控制日志) - 避免在过程名里塞版本号或日期,比如
sp_report_v2_2024——重用的前提是稳定,不是靠改名绕过维护
参数默认值只设 NULL,别设具体值如 0 或空字符串
设具体默认值等于悄悄改变调用方语义。比如 @in_status INT = 0,调用时没传参,结果过滤出所有 status=0 的记录;但业务上“不指定状态”本意是“不限制”,不是“只查禁用状态”。
常见错误现象:前端分页接口传了 @page_size = NULL,但过程里写成 ISNULL(@page_size, 20),导致后台导出全量数据时被截断。
实操建议:
- 所有参数默认为
NULL,并在开头用ISNULL()或CASE显式处理“未提供”的逻辑 - 对必填参数,在注释里写清约束,比如
-- @in_order_id: 必填,非零正整数 - 布尔类开关参数用
BIT类型,设默认= NULL,而不是= 0,留出“未启用”语义空间
别在存储过程里拼接 SQL 字符串执行 EXEC(@sql)
动态 SQL 看似灵活,实则直接杀死可重用性:执行计划无法复用、参数化失效、SQL 注入风险陡增、调试时根本看不到真实执行语句。
使用场景只有极少数例外:分表查询(如按月日志表)、兼容旧字段名的迁移过渡期。其余情况,99% 都能用 CASE WHEN、临时表或 CTE 替代。
实操建议:
- 用
IF @in_sort_by = 'created'分支代替拼接ORDER BY created DESC - 需要条件过滤字段时,用
AND (@in_category_id IS NULL OR category_id = @in_category_id) - 真要动态列,先用视图或计算列固化逻辑,而不是在过程里
EXEC
返回结果统一用 SELECT,别混用 RETURN、OUTPUT 参数或临时表
RETURN 只能传整数状态码,OUTPUT 参数难调试且客户端支持不一,临时表跨过程不可见——这三者都会让下游(尤其是 ORM 或 BI 工具)抓瞎。
性能影响明显:用 OUTPUT 返回 100 行数据,比直接 SELECT 多一次数据序列化;临时表还可能引发锁竞争。
实操建议:
- 所有业务数据一律走
SELECT,结构固定(字段名、类型、顺序不变) - 错误信息不塞进结果集,改用
THROW抛异常,由调用方捕获 - 需要多结果集?用多个
SELECT语句,现代驱动都支持(如 .NET 的SqlDataReader.NextResult())
最常被忽略的一点:重用不是靠“写得通用”,而是靠“边界清晰”。一个存储过程只做一件事,参数只暴露必要维度,返回结构不随内部逻辑调整而变——否则每次修改都在给下游埋雷。










