重用其他參數生成的執行計劃,導致當前參數採用非最優計劃。典型觸發:參數化預存程序 + 資料分布不均。
CREATE TABLE ParameterSniffProblem (
Id INT IDENTITY(1,1), CustomerId INT, OrderId INT,
OrderStatus INT, CreateDate DATETIME, Remark VARCHAR(200)
);
-- 正常分布:50萬筆,CustomerId 0~9999 均勻
DECLARE @I INT = 0;
WHILE @I < 500000 BEGIN
INSERT ParameterSniffProblem VALUES (@I % 10000, @I, RAND()*10, GETDATE()-RAND()*100, NEWID());
SET @I += 1;
END
-- 偏態資料:CustomerId=6666 佔 10 萬筆
INSERT ParameterSniffProblem VALUES (6666, RAND()*100000, 1, GETDATE()-RAND()*100, NEWID()) GO 100000;
CREATE CLUSTERED INDEX IDX_CreateDate ON ParameterSniffProblem(CreateDate);
CREATE INDEX IDX_CustomerId ON ParameterSniffProblem(CustomerId);
CREATE PROCEDURE ParameterSniffTest
@CustomerId INT, @Status INT, @FromDate DATETIME, @ToDate DATETIME
AS
BEGIN
DECLARE @Sql NVARCHAR(MAX) = N'SELECT * FROM ParameterSniffProblem WHERE 1=1';
IF @CustomerId IS NOT NULL SET @Sql += N' AND CustomerId=@CustomerId';
IF @Status IS NOT NULL SET @Sql += N' AND OrderStatus=@Status';
IF @FromDate IS NOT NULL SET @Sql += N' AND CreateDate>=@FromDate';
IF @ToDate IS NOT NULL SET @Sql += N' AND CreateDate<=@ToDate';
EXEC Sp_ExecuteSql @Sql,
N'@CustomerId INT, @Status INT, @FromDate DATETIME, @ToDate DATETIME',
@CustomerId, @Status, @FromDate, @ToDate;
END;
@CustomerId=100(正常分布)→ Index Seek,計劃緩存@CustomerId=6666(偏態分布)→ 重用上述計劃,仍走 Index Seek,實際需 Table Scan 更優DBCC FREEPROCCACHE 後重跑 6666 → 正確選擇 Table Scan


將易產生嗅探的參數(如 CustomerId)直接拼接入 SQL 字串,其他參數保持參數化:
IF @CustomerId IS NOT NULL
SET @Sql += N' AND CustomerId=' + CAST(@CustomerId AS NVARCHAR(20));
-- 其他參數維持參數化
IF @Status IS NOT NULL SET @Sql += N' AND OrderStatus=@Status';
@CustomerId=1 → Index Seek,計劃緩存@CustomerId=6666 → 觸發重編譯,正確選 Table Scan

| 優點 | 缺點 |
|---|---|
| 避免參數嗅探導致的災難性計劃 | CustomerId 變化即重編譯,增加編譯次數 |
| 保留其他參數的計劃重用優勢 | 動態 SQL 需防注入(此處數值型安全) |
| 實作簡單,無需 Plan Guide 維護 | 偏態值眾多且動態變化時效果有限 |
OPTION (RECOMPILE):每次重編譯,CPU 開銷大OPTIMIZE FOR:固定優化值,偏態動態變化無效部分參數化(嗅探參數拼接、其餘參數化)在開發效率、執行效能、計劃重用三者間取得實用平衡。核心原則:以最小代價解決問題,避免不合理計劃重用造成系統級效能災難。