原框架通用分頁存儲過程採用 NOT IN (SELECT TOP ...) 子查詢方式,導致重複掃描基礎表、無法有效使用索引,資料量增長時效能急劇下降。
INSERT INTO #TempIds,僅掃描一次基礎表。@@ROWCOUNT 取得總筆數計算總頁數,避免第二次 COUNT(*) 掃描。JOIN #TempIds 定位目標頁資料,再關聯主表取得完整欄位。CREATE PROCEDURE [dbo].[AreaSelect_Optimized]
@PageSize INT = 20,
@CurrentPage INT = 1,
@AreaName NVARCHAR(50) = NULL,
-- 其他參數...
AS
BEGIN
SET NOCOUNT ON;
IF @AreaName IS NOT NULL SET @AreaName = '%' + @AreaName + '%';
-- 1. 篩選符合條件的 ID 存入暫存表
CREATE TABLE #TempIds (Identifier INT PRIMARY KEY);
INSERT INTO #TempIds (Identifier)
SELECT Identifier FROM Area
WHERE (@AreaName IS NULL OR AreaName LIKE @AreaName)
-- 其他條件...
ORDER BY AreaName; -- 符合索引順序
DECLARE @TotalRows INT = @@ROWCOUNT;
DECLARE @TotalPages INT = CASE WHEN @TotalRows % @PageSize = 0
THEN @TotalRows / @PageSize ELSE @TotalRows / @PageSize + 1 END;
-- 2. 計算分頁範圍
DECLARE @StartRow INT = (@CurrentPage - 1) * @PageSize + 1;
DECLARE @EndRow INT = @CurrentPage * @PageSize;
-- 3. 使用 ROW_NUMBER 配合暫存表分頁 (SQL 2005+)
;WITH Numbered AS (
SELECT t.Identifier, ROW_NUMBER() OVER (ORDER BY t.Identifier) AS RowNum
FROM #TempIds t
)
SELECT a.Identifier, a.ParentId, a.AreaLevel, a.Children, a.AreaName, a.Path, a.Status, a.Alt,
@TotalPages AS TotalPage
FROM Area a
INNER JOIN Numbered n ON a.Identifier = n.Identifier
WHERE n.RowNum BETWEEN @StartRow AND @EndRow
ORDER BY a.AreaName;
DROP TABLE #TempIds;
END
| 指標 | 原始寫法 | 優化後 |
|---|---|---|
| 基礎表掃描次數 | 2~3 次 (COUNT + NOT IN 子查詢 + 最終查詢) | 1 次 (INSERT INTO #TempIds) |
| 索引利用 | 子查詢導致索引失效 | 暫存表叢集索引 + 主表索引 Seek |
| TempDB 壓力 | 低 | 適中 (僅存主鍵) |
| 大量資料效能 | 指數級下降 | 線性穩定 |
OFFSET ... FETCH 簡化分頁語法。CREATE CLUSTERED INDEX 明確指定索引順序。