重构SQL存储过程的核心目的是解决执行变慢、牵一发而动全身、修改成本高等真实问题;判断标准是ALTER PROCEDURE比写新逻辑更令人犹豫;需规范参数命名与类型、杜绝动态SQL注入与缓存失效、慎用临时表、强制TRY/CATCH错误处理并统一抽取校验逻辑。

重构 SQL 存储过程不是为了“看起来更漂亮”,而是解决真实问题:执行变慢、改一处崩三处、加个新字段就要重测整条链路。核心判断就一条——当 ALTER PROCEDURE 比写新逻辑还让人犹豫时,该重构了。
参数混乱导致调用失败或结果错乱
常见现象是传入 @StartDate 却没校验格式,或者多个同类型参数(如 @Filter1、@Filter2)语义模糊。SQL Server 不会自动类型转换所有场景,尤其遇到 datetime 和 varchar 混用时,容易静默截断或报错 Conversion failed when converting date and/or time from character string。
- 把所有输入参数按语义分组,比如筛选类统一前缀
@Where_(@Where_Status、@Where_DateFrom),避免@p1、@p2这类命名 - 对日期类参数强制使用
DATE或DATETIME2(3),别留VARCHAR接口再内部CONVERT - 输出参数只用于真正需要返回单值的场景(如计数、状态码),别用它传结果集——那是
SELECT的事
动态 SQL 缺少参数化引发注入或缓存失效
看到 EXEC sp_executesql @SQL 就要警惕:如果 @SQL 是拼出来的字符串(比如 'SELECT * FROM '+ @TableName),既可能被注入,又会让 SQL Server 为每个不同表名生成独立执行计划,吃光缓存。
- 表名、列名等元数据必须走白名单校验,比如查
sys.tables确认@TableName真实存在,再拼进 SQL - 所有业务值(如用户 ID、状态码)一律用参数占位符
@Param,绝不用+拼接 - 如果真要支持任意列查询,优先考虑用视图 +
WHERE过滤,而不是动态构建SELECT列表
临时表滥用拖慢并发和内存
#TempTable 看起来方便,但每执行一次都建表、写日志、占 tempdb 空间。高并发下容易触发 tempdb 争用,错误信息常是 Could not allocate space for object 'dbo.#TempTable' in database 'tempdb'。
- 能用 CTE 替代的就别建临时表,尤其只是做一次中间过滤(如
WITH Filtered AS (SELECT ...)) - 必须用临时表时,显式指定列和数据类型,别用
SELECT * INTO #T FROM ...—— 隐式结构不可控,且无法复用执行计划 - 大结果集写入临时表前,先估算行数;超过 5000 行考虑用物理表 + 分区清理,或直接走索引优化原表查询
错误处理停留在 RAISERROR 而非 TRY/CATCH
只用 RAISERROR 抛错,意味着事务不会自动回滚,上游应用收不到结构化错误码,日志里只剩一行 “消息 50000”,根本没法定位是哪条 UPDATE 失败。
- 所有修改数据的操作必须包在
BEGIN TRY ... END TRY BEGIN CATCH ... END CATCH里 -
CATCH块中至少记录ERROR_NUMBER()、ERROR_LINE()和ERROR_MESSAGE()到日志表,别只靠PRINT - 输出参数别在
CATCH外赋值,否则错误发生后仍返回脏数据
重构最难的不是语法,是把散落在十几处的相同校验逻辑(比如客户状态检查)抽成独立存储过程并统一调用——这需要 DBA 和开发对业务边界有共识,否则很容易变成“为解耦而解耦”,反而增加跨过程调用开销。

















