要获取SQL Server存储过程的真实执行计划,必须使用SET STATISTICS XML ON并实际执行(而非仅看估计计划),同时关联sys.dm_exec_query_stats、sys.dm_exec_sql_text和sys.dm_exec_query_plan按objectid过滤,并注意cached_time与last_execution_time差异。

怎么看 SQL Server 存储过程的真实执行计划
SQL Server 不会直接缓存“带参数的执行计划快照”,而是缓存语句级计划;sp_executesql 和 EXEC @sql 生成的动态 SQL,若未参数化,会导致大量相似计划堆积,掩盖真实瓶颈。
- 用
SET STATISTICS XML ON+ 实际执行(不是“显示估计的执行计划”)——只有真正跑起来,才能捕获参数嗅探后的实际计划 - 查缓存时别只看
sys.dm_exec_query_stats,要关联sys.dm_exec_sql_text和sys.dm_exec_query_plan,过滤出对应存储过程的objectid - 注意
cached_time和last_execution_time差异:计划可能已缓存但长期未重用,不代表当前逻辑仍有效
为什么改写 WHERE 条件顺序不总能优化性能
SQL Server 查询优化器基于统计信息估算行数,而非按代码书写顺序逐条判断。把“高筛选率条件”放前面,对最终计划没影响——但会影响 OR、IN 子句或函数包裹列的可SARG性。
-
WHERE YEAR(OrderDate) = 2023永远无法走索引;换成OrderDate >= '2023-01-01' AND OrderDate 才行 -
WHERE Status IN ('A','B') AND Amount > 1000中,如果Status区分度低(95% 是 'A'),而Amount有非空索引,优化器可能先走Amount索引再回表 - 避免在 WHERE 中对字段做计算或函数调用,否则索引失效,
ISNULL(Phone, '') = ''应改为Phone IS NULL
临时表 vs 表变量:执行计划缓存行为差异在哪
表变量(@tmp)没有统计信息,优化器始终假定返回 1 行;临时表(#tmp)有统计信息,但首次编译时若为空,仍可能生成次优计划——尤其在存储过程中多次重用时。
- 数据量 > 100 行,优先用
#tmp;小于 100 行且仅做简单过滤,@tmp开销更低 - 用
#tmp后记得加UPDATE STATISTICS #tmp(SQL Server 2016+ 可设WITH STATISTICS_NORECOMPUTE = OFF) - 不要在循环里反复
CREATE TABLE #tmp—— 每次都触发重编译,计划缓存失效;改用单次建表 +TRUNCATE
对比两个存储过程写法:怎么避免“看起来一样,执行却慢三倍”
差异常藏在隐式转换、参数类型不匹配、或 SET 选项上。比如一个过程用 varchar(50) 参数接收 nvarchar 字符串,就会导致索引列被隐式转成 nvarchar,全表扫描。
- 检查
sys.dm_exec_procedure_stats的execution_count和total_logical_reads,比对两过程单位执行的读页数 - 用
DBCC SHOW_STATISTICS对比关键索引的直方图更新时间,确认统计信息是否过期 - 在过程开头显式加
SET ANSI_NULLS ON; SET QUOTED_IDENTIFIER ON;—— 缺失会导致计划无法复用,尤其跨 SSMS 和应用连接时
最麻烦的是参数嗅探:同一个过程,第一次用小参数值编译,缓存了嵌套循环计划;后续大参数进来,还硬着头皮用那个计划。这时候不能只改写法,得结合 OPTION (RECOMPILE) 或 OPTIMIZE FOR 控制。










