存储过程首次运行后变快是因为执行计划被缓存并复用,但缓存脆弱易失效;需通过查询sys.dm_exec_cached_plans和sys.dm_exec_query_stats验证usecounts和execution_count是否递增,若usecounts恒为1则每次均重编译。

存储过程首次运行后变快,不是因为“编译一次、永久受益”,而是因为执行计划被缓存并成功复用了——但这个缓存非常脆弱,稍有不慎就失效。
怎么确认执行计划真的被缓存并复用了?
不能只看“第二次执行时间短”,得查 SQL Server 底层视图验证是否命中同一计划。常见误判是:以为快了就是缓存生效,结果发现 usecounts 始终为 1。
- 执行存储过程一次后,立即运行:
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不涨,说明每次都在重新编译——大概率触发了缓存绕过机制
哪些写法会让存储过程“假装快”,实则每次都重编译?
这些不是边缘情况,而是高频踩坑点,它们让存储过程失去缓存优势,甚至比参数化即席查询还慢。
-
EXEC(@sql)或sp_executesql N''拼接完整语句(哪怕只拼 WHERE 条件),SQL Server 无法生成稳定计划 - 显式写了
WITH RECOMPILE,等于主动放弃缓存 - 单条语句末尾加了
OPTION (RECOMPILE),只对该语句生效,但同样跳过缓存 - 参数类型不一致:客户端传
int,而存储过程定义为smallint,引发隐式转换 + 计划重编译 - 数据库兼容级别
为什么有时候即席查询比存储过程还快?
当缓存失效成为常态,存储过程的“预编译”优势就反转成负担:多一层解析+校验+权限检查,还可能因参数嗅探(parameter sniffing)选错计划。
- 用
sp_executesql做参数化即席查询,只要参数类型一致、文本完全相同,也能进缓存,且无存储过程的额外元数据开销 - 存储过程中含大量分支逻辑(
IF @flag = 1后跟不同查询),优化器可能生成“平均主义”计划,对任一路径都不优 - 网络往返少 ≠ 性能高:若业务只需单条查询,调用存储过程反而多一次 RPC 开销;但若含插入+查
SCOPE_IDENTITY()+ 日志写入,封装后确实省掉 3 次 RTT
真正影响性能的,从来不是“有没有存储过程”,而是执行计划是否稳定复用、参数是否干净、语句结构是否可预测。缓存不是开关,是状态——它需要你持续观察 sys.dm_exec_cached_plans,而不是依赖“应该快”的直觉。










