存储过程本身不自动提升性能,真正起作用的是SQL合理性、索引存在性及执行计划高效性;应避免SELECT *、确保WHERE字段有合适索引、避免函数操作导致索引失效。

直接说结论:存储过程本身不自动提升性能,真正起作用的是里面写的SQL是否合理、索引是否存在、执行计划是否高效。很多团队把逻辑塞进存储过程就以为“优化完成了”,结果发现执行时间没变,甚至更慢。
避免在存储过程中写 SELECT *
这是最常见也最容易被忽略的性能陷阱。SELECT * 会强制数据库读取所有列,哪怕业务只用其中2个字段;如果表有大字段(如 TEXT、BLOB),还会触发额外的磁盘 I/O 和内存拷贝。
- 即使表结构简单,
SELECT *也会让覆盖索引失效——因为索引里没存全部字段,必须回表查,多一次随机 I/O - 网络传输量翻倍,尤其当调用方是远程应用时,延迟明显上升
- 后续加字段或改类型时,
SELECT *可能意外带出敏感字段(比如密码哈希、身份证号)
实操建议:明确列出需要的字段,例如把 SELECT * FROM orders WHERE status = @status 改成 SELECT order_id, user_id, amount, created_at FROM orders WHERE status = @status。
确保 WHERE 条件字段上有有效索引
存储过程里再快的逻辑,如果底层查询走全表扫描,整体就是慢的。索引不是“建了就完事”,得看它是否真被用上。
- 检查执行计划:运行
SET STATISTICS XML ON后执行存储过程,找<IndexScan>或<TableScan>节点——出现后者基本等于没索引 - 联合索引要注意顺序:
WHERE department_id = 5 AND created_at > '2024-01-01',索引应建为(department_id, created_at),反过来就只能用到第一个字段 - 避免在 WHERE 字段上套函数:
WHERE YEAR(created_at) = 2024会让索引失效;改写成WHERE created_at >= '2024-01-01' AND created_at
别用游标(CURSOR)遍历数据
游标是存储过程里最典型的“性能杀手”。它把集合操作硬生生拆成单行循环,CPU 和 TempDB 压力陡增,执行时间常随数据量线性甚至指数增长。
- 典型错误模式:
DECLARE cur CURSOR FOR SELECT id FROM large_table WHERE ...+FETCH NEXT循环更新 - 99% 的场景可用集合操作替代:用
UPDATE ... JOIN、MERGE或 CTE + 窗口函数一次性处理整批数据 - 实在绕不开(比如需调用外部 API 每行发请求),至少声明为
STATIC READ_ONLY,避免锁升级和版本控制开销
示例:原游标逻辑更新用户积分,可改写为 UPDATE u SET points = points + t.delta FROM users u JOIN #temp_updates t ON u.id = t.user_id。
参数嗅探问题必须主动应对
SQL Server 会缓存第一次执行时生成的执行计划,并复用给后续调用——但如果首次传入的是极端值(比如查“状态=已取消”的订单,只占0.1%),而下次查“状态=待发货”(占80%),复用的计划就很可能是错的。
- 现象:同一存储过程,不同参数下执行时间差异巨大(几毫秒 vs 几秒)
- 临时解法:加
OPTION (RECOMPILE)强制每次重编译(适合低频调用) - 长期方案:对关键参数使用
OPTIMIZE FOR (@param = value)或拆分成多个专用存储过程 - 注意:
WITH RECOMPILE创建时加,效果类似但更重,不推荐常规使用
真正难缠的是那些“看似稳定、实则分布偏斜”的参数,比如按地区查订单——一线城市数据量是小城市的百倍,但都用同一个执行计划跑,很容易踩坑。


















