大量資料前端分頁會導致網路傳輸暴增、Web 伺服器記憶體壓力大、回應逾時。JDBC 支援原生分頁(LIMIT/OFFSET 推送至資料庫),但傳統 ADO.NET 需自行實作。儲存程序分頁是經典解法。
| 參數 | 型別 | 說明 |
|---|---|---|
| @sqlQuery | VARCHAR(2000) | 完整 SELECT 查詢或資料表名稱 |
| @pageSize | INT | 每頁筆數 |
| @pageIndex | INT | 當前頁碼(1-based) |
##TB1516_,注入 IDENTITY(numeric,1,1) AS ID1516 作為分頁鍵SELECT *, IDENTITY... INTO ... FROM 表名ID1516 BETWEEN @start AND @end 取得頁面資料MAX(ID1516) 為總筆數CREATE PROC execByPage
@sqlQuery VARCHAR(2000),
@pageSize INT,
@pageIndex INT
AS
BEGIN
SET NOCOUNT ON
SET ANSI_WARNINGS OFF
DECLARE @tmpTableName VARCHAR(50) = '##TB1516_' + REPLACE(CAST(NEWID() AS VARCHAR(40)), '-', '')
DECLARE @subIndex INT = CHARINDEX('from', @sqlQuery)
IF @subIndex > 0
BEGIN
DECLARE @sqlQuery1 VARCHAR(2000) = SUBSTRING(@sqlQuery, 1, @subIndex - 1)
DECLARE @sqlQuery2 VARCHAR(2000) = SUBSTRING(@sqlQuery, @subIndex, LEN(@sqlQuery))
SET @sqlQuery = @sqlQuery1 + ',IDENTITY(numeric,1,1) as ID1516 INTO ' + @tmpTableName + ' ' + @sqlQuery2
END
ELSE
SET @sqlQuery = 'SELECT *,IDENTITY(numeric,1,1) as ID1516 INTO ' + @tmpTableName + ' FROM ' + @sqlQuery
EXEC(@sqlQuery)
DECLARE @indexStart VARCHAR(20) = CAST((@pageIndex - 1) * @pageSize + 1 AS VARCHAR(20))
DECLARE @indexEnd VARCHAR(20) = CAST(@pageIndex * @pageSize AS VARCHAR(20))
EXEC('SELECT * FROM ' + @tmpTableName + ' WHERE ID1516 BETWEEN ' + @indexStart + ' AND ' + @indexEnd)
EXEC('SELECT MAX(ID1516) AS recordCount FROM ' + @tmpTableName)
EXEC('DROP TABLE ' + @tmpTableName)
END
EXEC execByPage
'SELECT OrderID, CustomerID, OrderDate FROM Orders WHERE Status = 1 ORDER BY OrderDate DESC',
20, -- 每頁 20 筆
3 -- 第 3 頁
| 優點 | 缺點 |
|---|---|
| 零侵入:原 SQL 不動 | 雙重掃描:建暫存表 + 分頁查詢 |
| 支援任意複雜查詢、複合鍵 | tempdb 壓力大、高併發下爭用 |
| 相容舊版 SQL Server (2000+) | 動態 SQL 防注入需自行處理 |
| 無游標、WHILE 迴圈 | OFFSET/FETCH 原生語法效能更優 |
SELECT OrderID, CustomerID, OrderDate
FROM Orders
WHERE Status = 1
ORDER BY OrderDate DESC
OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY -- 第 3 頁,每頁 20 筆
原生語法:單掃描、優化器感知、計畫快取友善、支援參數化。新專案首選。