實測案例:將字串常數 `'3810'` 改為變數 `@mis_company_code` 後,執行時間從 1 秒飆升至 7 秒。根因:**參數嗅探** 導致優化器為變數產生「通用計畫」,而非常數的「最佳計畫」。
-- 版本 A:常數 (1 秒)
SELECT ACC_SEGMENT
FROM dbo.mis_gl_balance
WHERE LEFT(ACC_SEGMENT, 22) IN (
SELECT '3810.105301.' + mis_code
FROM finance_budget_account
WHERE budget_type = '電路租費'
);
-- 版本 B:變數 (7 秒)
DECLARE @mis_company_code CHAR(4) = '3810';
SELECT ACC_SEGMENT
FROM dbo.mis_gl_balance
WHERE LEFT(ACC_SEGMENT, 22) IN (
SELECT @mis_company_code + '.105301.' + mis_code
FROM finance_budget_account
WHERE budget_type = '電路租費'
);
| 方案 | 語法/做法 | 優點 | 缺點/風險 |
|---|---|---|---|
| 1. OPTION (RECOMPILE) | 查詢尾加 `OPTION (RECOMPILE)` | 每次編譯看見實際值,零快取污染 | 編譯 CPU 開銷,高頻查詢慎用 |
| 2. OPTIMIZE FOR (@param = '典型值') | `OPTION (OPTIMIZE FOR (@mis_company_code = '3810'))` | 指定優化目標值,計畫可重用 | 若實際值分布差異大仍會失誤 |
| 3. OPTIMIZE FOR UNKNOWN | `OPTION (OPTIMIZE FOR (@mis_company_code UNKNOWN))` | 強制用密度向量,行為穩定 | 放棄嗅探優勢,可能非最佳 |
| 4. 動態 SQL + sp_executesql | 將變數內嵌為字面值組裝 SQL 字串執行 | 編譯時見到實際值,計畫可針對性快取 | 需防注入,代碼較複雜 |
| 5. 參數化改為常數 (僅限極少數固定值) | 用 `IF @code='3810' SELECT ... ELSE SELECT ...` | 各分支各自最佳計畫 | 值多時維護困難,違反 DRY |
CREATE PROCEDURE GetBalance @CompanyCode CHAR(4)
AS
SELECT ... WHERE CompanyCode = @CompanyCode
OPTION (OPTIMIZE FOR (@CompanyCode = '3810')); -- 或 RECOMPILE
理解參數嗅探機制,善用查詢提示與動態 SQL,即可避免「改成參數化就變慢」的反直覺陷阱。