存储过程本身不是性能瓶颈,问题在于SQL写法、参数使用和执行计划稳定性;需避免参数嗅探、索引列上函数运算及隐式转换,优先用局部变量隔离参数或启用查询存储强制计划。

直接说结论:存储过程本身不是性能瓶颈,问题出在内部 SQL 写法、参数使用和执行计划稳定性上。优化重点不在“重写存储过程”,而在“让每次执行都走对的执行计划、用对的索引、避免隐式转换和低效结构”。
避免参数嗅探导致执行计划失效
SQL Server 根据第一次传入的参数值生成执行计划并缓存,后续不同参数可能复用该计划,造成严重性能抖动。典型现象是:同一个 sp_GetOrdersByStatus,传 @Status = 'Shipped' 很快,传 @Status = 'Pending' 却卡住几秒。
- 临时方案:加
OPTION (RECOMPILE)强制每次重编译(适合调用频率低、参数差异大的场景) - 稳妥方案:用局部变量“断开”参数嗅探链——把输入参数赋给
DECLARE @LocalStatus VARCHAR(20) = @Status,后续查询全用@LocalStatus - 高级方案:启用查询存储 + 强制计划(
sys.sp_query_store_force_plan),锁定已验证高效的执行计划
别在 WHERE 条件里对索引列用函数或表达式
哪怕只是 WHERE YEAR(OrderDate) = 2025 或 WHERE LEFT(Phone, 3) = '138',也会让 SQL Server 放弃使用 OrderDate 或 Phone 上的索引,退化为全表扫描。
- 改成范围条件:
WHERE OrderDate >= '2025-01-01' AND OrderDate - 字符串前缀匹配改用
LIKE '138%'(确保字段有合适索引) - 如果必须用函数结果过滤,考虑持久化计算列 + 索引:
ALTER TABLE Orders ADD OrderYear AS YEAR(OrderDate) PERSISTED,再在OrderYear上建索引
用集合操作代替游标和循环
游标逐行处理在 SQL Server 中代价极高,尤其在大数据量下会放大锁持有时间、阻塞并发、消耗大量 tempdb 空间。
- 删除游标逻辑,改用
UPDATE ... FROM、MERGE或 CTE + 集合更新 - 批量插入不用循环
INSERT INTO ... VALUES (...),改用INSERT INTO ... SELECT或INSERT INTO ... VALUES (...), (...), (...) - 需要按顺序处理且依赖前一行结果?优先评估是否真不能用窗口函数替代(如
LAG()、SUM() OVER ())
动态 SQL 要防注入,更要防计划缓存污染
用 sp_executesql 拼接语句很常见,但若拼接逻辑导致每条语句文本都不同(比如把参数值直接拼进字符串),SQL Server 就无法复用执行计划,频繁编译拖慢性能。
- 永远用参数化:传参用
@Param占位符,不要拼字符串 - 避免在动态 SQL 中拼表名/列名——除非真需要,否则用
IF分支或视图/CTE 替代 - 检查
sys.dm_exec_query_stats中相同逻辑的语句是否生成了多个plan_handle;如果是,说明缓存被污染
最常被忽略的点:很多“慢存储过程”根本没在查数据,而是在等锁、等日志刷盘、或被其他会话长时间阻塞。上线前务必用 sys.dm_exec_requests 和 sys.dm_os_waiting_tasks 抓一次真实执行时的等待类型,而不是只盯着 CPU 时间。


















