参数嗅探导致执行时间忽高忽低,本质是SQL Server复用首次生成的执行计划而未适配后续参数分布差异;OPTIMIZE FOR UNKNOWN按统计信息平均密度生成稳定中庸计划,适用于高频调用且数据倾斜不极端场景。

怎么一眼判断是参数嗅探在作怪
存储过程执行时间忽高忽低,比如同一 EXEC sp_get_orders @cust_id = 123 有时 50ms,有时 8s;把语句拿出来改成 WHERE cust_id = 123 直接跑就稳定快——这基本就是参数嗅探。它不是代码写错了,而是 SQL Server 缓存了第一次调用时生成的执行计划,后续参数值分布差异大(比如查热门客户 vs 查冷门客户),复用旧计划导致走错索引、误判行数、全表扫描。
OPTIMIZE FOR UNKNOWN 是什么,为什么比 RECOMPILE 更稳
OPTION (OPTIMIZE FOR UNKNOWN) 告诉优化器:别看这次传进来的 @cust_id 是多少,编译时直接按列统计信息里的平均密度来估算行数,生成一个“中庸但靠谱”的计划。它不重编译,只改优化逻辑,所以 CPU 开销低、并发友好。
- 适用于高频调用的存储过程(比如每秒几十次的订单查询)
- 对数据倾斜不极端的场景效果明显(比如客户订单量大多在 1–100 单之间,没有单个客户占 90% 数据)
- 生成的计划是稳定的,不会每次执行都波动,监控和调优更可控
- 不能加在
CREATE PROCEDURE语句末尾,必须写在具体查询语句后面,例如:SELECT * FROM orders WHERE cust_id = @cust_id OPTION (OPTIMIZE FOR UNKNOWN);
OPTIMIZE FOR (@param = value) 什么时候该用
当你明确知道某个参数值最常出现、且它的数据分布最具代表性,就可以用 OPTION (OPTIMIZE FOR (@cust_id = 456))。优化器会按这个固定值生成计划,后续所有调用都按此估算——不是“猜”,是“指定参考值”。
- 适合参数分布高度集中(比如 80% 请求都是查
@status = 'Shipped') - 注意:如果实际请求里突然大量出现
@status = 'Pending',而你又锁定了'Shipped',可能反而更慢 - 不能写成
OPTION (OPTIMIZE FOR (@cust_id = @cust_id)),语法错误;必须是字面值或UNKNOWN - 可配合
sys.dm_exec_procedure_stats查avg_logical_reads波动,确认是否真存在典型值
容易被忽略的细节和坑
很多人加了 OPTION (OPTIMIZE FOR UNKNOWN) 还是慢,问题往往不在提示本身,而在上下文:
- 它只影响紧挨着它的那一条语句,如果存储过程里有多个
SELECT,得每个都加,漏一个就可能拖垮整体 - 如果语句里用了局部变量(如
DECLARE @local_id INT = @cust_id),SQL Server 本来就不嗅探,再加OPTIMIZE FOR UNKNOWN属于冗余,无害但没必要 - 兼容级别低于 120(SQL Server 2014)时,
OPTIMIZE FOR UNKNOWN行为可能不一致,建议先查SELECT compatibility_level FROM sys.databases WHERE name = DB_NAME(); - 它解决不了统计信息过期问题——如果表数据暴涨但没更新统计信息,再“中庸”的估算也是错的,记得定期
UPDATE STATISTICS

















