答案是sql server因arithabort等set选项差异为ssms和c#生成不同执行计划,导致性能偏差;可通过查询缓存验证多计划存在,推荐优先使用option(recompile)、统一arithabort或sp_recompile修复。

这不是代码写错了,也不是网络或硬件问题——大概率是 SQL Server 为 SSMS 和你的程序生成了两套不同的执行计划,而且其中一套已经失效或不适用。
为什么 SSMS 和 C# 用的不是同一个执行计划
SQL Server 对「不同 SET 选项」的会话创建独立的缓存条目。最典型的就是 ARITHABORT:SSMS 默认是 ARITHABORT OFF,而 .NET 的 SqlCommand 默认是 ARITHABORT ON。哪怕存储过程完全一样、参数完全一样,这两个会话也会各自缓存一份执行计划。
- 如果某次 SSMS 调用用了极端参数(比如空值、极小/极大 ID),生成的计划可能只适合那种场景;
- 而 C# 第一次调用时用了常见参数,却因
ARITHABORT不同,被迫走另一条缓存路径,结果复用了那个“偏科”的旧计划; - 更糟的是,表结构变更、统计信息过期、数据倾斜加剧后,旧计划可能彻底跑偏。
怎么快速验证是不是执行计划问题
别猜,直接查缓存里有没有两份计划:
- 在 SSMS 中运行:
SELECT cp.plan_handle, cp.usecounts, cp.cacheobjtype, cp.objtype, st.text FROM sys.dm_exec_cached_plans cp CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st WHERE st.text LIKE '%你的存储过程名%'; - 观察
usecounts是否明显不均,或plan_handle是否有多个; - 对每个
plan_handle执行:SELECT * FROM sys.dm_exec_query_plan(<code>plan_handle),对比 XML 执行计划里的ParameterList和实际参数值是否匹配。
三种可立即生效的修复方式(按推荐顺序)
优先选轻量、可控的方案:
-
加
OPTION (RECOMPILE):在存储过程内部关键语句末尾加上,比如SELECT ... FROM ... WHERE ... OPTION (RECOMPILE)。它让该语句每次执行都重编译,绕过参数嗅探,但仅影响这一句,开销最小; -
统一
ARITHABORT设置:在 C# 连接字符串末尾加;ARITHABORT=True(与 SSMS 默认一致),或在 SqlCommand 执行前显式执行SET ARITHABORT ON; -
强制刷新存储过程缓存:运行
EXEC sp_recompile '你的存储过程名',下次调用时重建计划——适合紧急恢复,但只是临时解法。
容易被忽略的陷阱
很多团队修完就以为万事大吉,结果过几天又慢了:
- 加了
WITH RECOMPILE到存储过程定义里?小心——这会让整个过程每次调用都重编译,高并发下 CPU 压力陡增; - 用本地变量代替参数(如
DECLARE @x = @input)?这会关闭参数嗅探,但 SQL Server 只能按“平均分布”估算行数,往往比坏计划还糟; - 没更新统计信息?
UPDATE STATISTICS或开启自动更新(AUTO_UPDATE_STATISTICS)才是治本前提,否则新计划也可能是错的。
真正稳定的方案,是把 OPTION (RECOMPILE) 加在最耗时的那条语句上,同时确保统计信息不过期——而不是指望一次 sp_recompile 永久解决。











