同樣邏輯的 SQL,將常量改為變量後執行時間從 1 秒飆升至 7 秒。這不是玄學,而是參數嗅探與基數估算機制在特定條件下的典型副作用。本文剖析根因,提供多種規避方案。
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 = '電路租費'
);
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 = '電路租費'
);
`LEFT(ACC_SEGMENT, 22)` 導致列上函數操作,**索引不可用**(Non-SARGable)。但常量版通過子查詢常量折疊,優化器將 `IN` 列表轉為常量集合,配合統計信息仍能選擇較優計劃。變量版喪失常量折疊能力,雙重打擊:列函數 + 基數誤估。
-- 創建覆蓋索引 CREATE INDEX IX_mis_gl_balance_ACC_SEGMENT_22 ON dbo.mis_gl_balance (LEFT(ACC_SEGMENT, 22)) INCLUDE (ACC_SEGMENT); -- 改寫查詢 SELECT b.ACC_SEGMENT FROM dbo.mis_gl_balance b JOIN finance_budget_account a ON LEFT(b.ACC_SEGMENT, 22) = '3810.105301.' + a.mis_code WHERE a.budget_type = '電路租費';
優勢:消除 `IN` 子查詢、利用索引、常量參與 JOIN 條件優化器可見。
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 = '電路租費'
) OPTION (RECOMPILE);
優勢:運行時嗅探變量值,生成最優計劃。代價:每次編譯 CPU 開銷,高頻調用慎用。
DECLARE @mis_company_code CHAR(4) = '3810'; DECLARE @codes TABLE (code CHAR(22) PRIMARY KEY); INSERT @codes SELECT @mis_company_code + '.105301.' + mis_code FROM finance_budget_account WHERE budget_type = '電路租費'; SELECT b.ACC_SEGMENT FROM dbo.mis_gl_balance b JOIN @codes c ON LEFT(b.ACC_SEGMENT, 22) = c.code;
優勢:物化後統計信息準確,計劃穩定。適用:中間結果集 < 10 萬行。
DECLARE @sql NVARCHAR(MAX) = N'
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 = N''電路租費''
)';
EXEC sp_executesql @sql;
風險:SQL 注入、計劃緩存污染、權限提升。僅作為最後手段。
ALTER TABLE dbo.mis_gl_balance ADD ACC_SEGMENT_22 AS LEFT(ACC_SEGMENT, 22) PERSISTED; CREATE INDEX IX_ACC_SEGMENT_22 ON dbo.mis_gl_balance (ACC_SEGMENT_22);
優勢:將函數計算移至寫入時,查詢直接 SARGable,彻底解決列函數問題。
| 方案 | 預估耗時 | 適用場景 | 維護成本 |
|---|---|---|---|
| JOIN + 覆蓋索引 | < 100ms | 高頻核心查詢 | 低(一次性建索引) |
| OPTION (RECOMPILE) | < 200ms | 低頻/臨時查詢 | 極低 |
| 臨時表物化 | < 300ms | 中等頻率、中間結果小 | 低 |
| 計算列持久化 | < 50ms | 核心高頻、寫入可接受開銷 | 中(DDL 變更) |
「常量快、變量慢」本質是優化器信息不對稱。解決路徑核心只有兩條:讓優化器看見值(RECOMPILE/動態 SQL/常量內聯)或讓優化器不依賴值也能選對計劃(JOIN 改寫/計算列/索引覆蓋/統計信息優化)。生產環境首選**結構優化(索引/計算列)+ 寫法優化(JOIN)**,輔以 `RECOMPILE` 戰術補丁,構建穩健性能基線。