存储过程快的前提是执行计划被成功缓存并复用;若因WITH RECOMPILE、EXEC(@sql)、OPTION(RECOMPILE)或参数类型不一致导致缓存失效,则可能比参数化即席查询更慢。

存储过程并不“天然”比直接写 SQL 快——它快的前提是执行计划被成功缓存并复用。一旦缓存失效、参数化不当或语句结构破坏了可重用性,存储过程反而可能比参数化即席查询更慢。
执行计划是否真的被复用了?查 sys.dm_exec_cached_plans 和 sys.dm_exec_query_stats
缓存不是黑箱,SQL Server 提供了可验证的视图。只看“执行时间变短”不等于计划被复用;必须确认是否命中同一 plan_handle:
- 执行一次存储过程后,运行:
SELECT plan_handle, cacheobjtype, objtype, usecounts, size_in_bytes FROM sys.dm_exec_cached_plans WHERE objtype = 'Proc'
找到对应存储过程的plan_handle - 再查该
plan_handle在sys.dm_exec_query_stats中的execution_count是否随调用递增 - 如果
usecounts == 1且execution_count不涨,说明每次都在编译新计划——大概率是用了WITH RECOMPILE、OPTION (RECOMPILE),或内部有EXEC(@sql)拼接
哪些写法会让执行计划缓存彻底失效?
缓存失效不是偶然,而是由明确语法或选项触发的。常见踩坑点包括:
- 在存储过程中使用
EXEC(@sql)或sp_executesql N''动态拼接完整语句(哪怕只拼 WHERE 条件),会导致无法生成稳定计划 - 显式加
WITH RECOMPILE选项,强制每次重新编译——适合数据分布剧烈变化的场景,但会抹掉所有缓存收益 - 在语句末尾加
OPTION (RECOMPILE),作用粒度更细,但也绕过缓存 - 参数类型不一致:客户端传
int,而存储过程定义为smallint,可能触发隐式转换+计划重编译 - 数据库兼容级别低于 120 时,简单参数化规则更严格,部分即席语句也难进缓存,反而让存储过程优势更明显
网络往返和语句打包带来的实际收益,常被低估
执行计划缓存只是优势之一;对高频、多步操作,减少 RPC 往返才是更实在的提速点:
- 一个含插入主表 + 插入明细 +
SCOPE_IDENTITY()+ 记日志的逻辑,封装成存储过程只需 1 次网络请求;拆成 4 条独立 SQL 至少产生 4 倍 TCP RTT 延迟 - 客户端用字符串拼接发送
INSERT INTO t VALUES ('a', 'b', ...),每行值不同 → 每次都是新语句 → 都要走完整编译流程 → 缓存利用率接近 0 - 存储过程参数传递的是二进制值,不是文本 SQL;100 字符的参数名 + 4 字节整数,远小于发送 200 字符的完整 INSERT 语句
真正决定快不快的,不是“有没有用存储过程”,而是“执行计划有没有稳稳地留在 sys.dm_exec_cached_plans 里,以及你有没有无意中用 EXEC 或 RECOMPILE 把它踢出去”。

















