既有通用分頁存儲過程每次查詢皆對大表 Area 重複掃描多次(統計總筆數、取分頁資料、多次 WHERE 子句),導致效能隨資料量增長急劇下降。
COUNT(Identifier) 需全表掃描,耗資源。NOT IN (SELECT TOP N ...) 分頁方式在大偏移量時效能極差。
-- 1. 將符合條件的資料一次性寫入臨時表
SELECT Identifier, ParentId, AreaLevel, Children, AreaName, Path, Status, Alt
INTO #temp_Area
FROM Area
WHERE (@Identifier IS NULL OR Identifier = @Identifier)
AND (@ParentId IS NULL OR ParentId = @ParentId)
... -- 其餘條件
ORDER BY AreaName ASC;
-- 2. 統計總筆數
DECLARE @SumCount INT;
SELECT @SumCount = COUNT(Identifier) FROM #temp_Area;
-- 3. 計算總頁數
DECLARE @TotalPage INT;
IF (@SumCount % @PageSize = 0) SET @TotalPage = @SumCount / @PageSize;
ELSE SET @TotalPage = ROUND(@SumCount / @PageSize, 0) + 1;
-- 4. 取分頁資料
SELECT TOP (@PageSize) Identifier, ParentId, AreaLevel, Children, AreaName, Path, Status, Alt,
@TotalPage AS TotalPage, @SumCount AS SumCount
FROM #temp_Area
WHERE Identifier NOT IN (
SELECT TOP (@PageSize * (@CurrentPage - 1)) Identifier FROM #temp_Area
)
ORDER BY AreaName ASC;
效果: 主表僅掃描一次,後續操作皆在記憶體臨時表上,IO 大幅降低。
發現 COUNT(Identifier) 仍有開銷,利用 SELECT INTO 完成後 @@ROWCOUNT 即可取得影響行數,省去額外掃描。
SELECT Identifier, ParentId, AreaLevel, Children, AreaName, Path, Status, Alt
INTO #temp_Area
FROM Area
WHERE ... -- 條件同前
ORDER BY AreaName ASC;
DECLARE @SumCount INT = @@ROWCOUNT; -- 直接取得寫入臨時表的行數
-- 後續分頁邏輯不變
關鍵點: @@ROWCOUNT 緊跟在 SELECT INTO 之後,捕捉該陳述式影響的行數,避免第二次掃描臨時表。
SELECT Identifier, ParentId, AreaLevel, Children, AreaName, Path, Status, Alt
FROM Area
WHERE ... -- 條件
ORDER BY AreaName ASC
OFFSET (@CurrentPage - 1) * @PageSize ROWS
FETCH NEXT @PageSize ROWS ONLY;
搭配 COUNT(*) OVER() 窗口函數可單查詢同時取得總筆數與分頁資料,為最佳解。若受限於 SQL Server 2005/2008 R2,臨時表 + @@ROWCOUNT 是相容性最好的高效方案。
| 版本 | 主表掃描次數 | 臨時表掃描次數 | 備註 |
|---|---|---|---|
| 原始 | 3+ | 0 | 重複 WHERE、COUNT 全掃描、NOT IN 分頁差 |
| 臨時表 | 1 | 2 | COUNT 掃描臨時表 |
| 臨時表+@@ROWCOUNT | 1 | 1 | 無額外 COUNT,最優 |
核心原則:將複雜篩選條件下推至單次掃描,利用臨時表物質化中間結果,善用系統變數避免重複統計。