巢狀子查詢 (如 IN (SELECT ...)) 在大資料量下常導致效能災難。本文透過實戰案例展示暫存表如何將執行時間從分鐘級降至秒級。
SELECT KeyId, COUNT(1) AS Num
FROM Table1
WHERE CreateDate >= '2007-09-21'
AND KeyId IN (SELECT KeyId FROM Table2 WHERE Id = 1611)
GROUP BY KeyId;
症狀:Table1 每列掃描都執行一次子查詢,隨 Table1 增長呈線性惡化。
SELECT a.KeyId, COUNT(1) AS Num
FROM Table1 a
JOIN (SELECT KeyId FROM Table2 WHERE Id = 1611) b ON a.KeyId = b.KeyId
WHERE a.CreateDate >= '2007-09-21'
GROUP BY a.KeyId;
優化器有時仍選擇巢狀迴圈,且衍生表無統計資訊,效能不穩定。
' 1. 將子查詢結果具體化至暫存表 (僅掃描 Table2 一次)
SELECT KeyId INTO #TmpKeys FROM Table2 WHERE Id = 1611;
' 2. 主查詢 JOIN 暫存表 (有統計資訊、可建立索引)
SELECT a.KeyId, COUNT(1) AS Num
FROM Table1 a
JOIN #TmpKeys b ON a.KeyId = b.KeyId
WHERE a.CreateDate >= '2007-09-21'
GROUP BY a.KeyId;
' 3. 清理
DROP TABLE #TmpKeys;
| 機制 | 說明 |
|---|---|
| 具體化 | 子查詢結果實體化,避免重複執行 |
| 統計資訊 | 暫存表自動建立統計資訊,優化器能估算基數選擇最佳連接策略 |
| 可索引 | 可對 #TmpKeys(KeyId) 建立叢集/非叢集索引,加速 JOIN |
| 範圍隔離 | # 區域暫存表僅在當前工作階段可見,並發無衝突 |
' 明確建立索引 (大量資料時顯著提升)
CREATE CLUSTERED INDEX IX_TmpKeys_KeyId ON #TmpKeys(KeyId);
' 使用 TABLE 變數替代 (小資料量、避免重編譯)
DECLARE @TmpKeys TABLE (KeyId INT PRIMARY KEY);
INSERT @TmpKeys SELECT KeyId FROM Table2 WHERE Id = 1611;
-- 但 TABLE 變數無統計資訊,優化器假設 1 列,大資料量慎用
Table Spool、Lazy Spool、估算列數嚴重偏離APPLY / JOIN| 寫法 | 執行時間 | 邏輯讀取 |
|---|---|---|
| 原始 IN 子查詢 | 4 分 12 秒 | 2,850,000 |
| 衍生表 JOIN | 1 分 08 秒 | 1,200,000 |
| 暫存表 + 索引 | 3.2 秒 | 45,000 |
暫存表不是銀彈,但在「大表驅動小表、中間結果可複用」場景下,是改寫複雜查詢的利器。