建立資料表 R_Student 並插入 100 萬筆測試資料:
CREATE TABLE R_Student (
Id NVARCHAR(64) PRIMARY KEY,
Class NVARCHAR(64) NOT NULL,
Age TINYINT NOT NULL,
Sex TINYINT NOT NULL
);
-- 插入 1,000,000 筆
DECLARE @i INT = 0;
WHILE @i < 1000000 BEGIN
INSERT INTO R_Student VALUES ('Name'+STR(@i), 'Class'+STR(@i), @i%100, @i%2);
SET @i += 1;
END
利用 SqlDataReader 僅前移游標特性,先 Read() 跳過前 N 筆(不取資料),再讀取目標頁筆數。優點:不載入不需資料,記憶體佔用極低。
public DataSet RangeQuery(string sql, long first, long last) {
using (var conn = new SqlConnection(connStr))
using (var cmd = new SqlCommand(sql, conn)) {
conn.Open();
using (var reader = cmd.ExecuteReader()) {
// 跳過前 first 筆
for (long i = 0; i < first && reader.Read(); i++) { }
// 讀取目標頁
var ds = new DataSet();
var dt = new DataTable();
ds.Tables.Add(dt);
dt.Load(reader); // Load 會讀取剩餘所有,需配合 last 限制
return ds;
}
}
}
關鍵優化:搭配 SELECT TOP (pageSize) ... WHERE ... ORDER BY Id 並在應用層控制 first、last,或直接在 SQL 加 OFFSET/FETCH (SQL Server 2012+)。
-- 第 1 頁
SELECT TOP @pageSize * FROM R_Student ORDER BY Id;
-- 第 N 頁 (N>1)
SELECT TOP @pageSize * FROM R_Student
WHERE Id NOT IN (SELECT TOP @pageSize*(@page-1) Id FROM R_Student ORDER BY Id)
ORDER BY Id;
缺點:NOT IN 子查詢效能隨頁碼遞增急劇惡化,且需主鍵唯一。
WITH Numbered AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY Id) AS RowNum FROM R_Student
)
SELECT * FROM Numbered
WHERE RowNum BETWEEN (@page-1)*@pageSize+1 AND @page*@pageSize;
優點:不受排序欄位、重複鍵限制,SQL Server 2005+ 支援,執行計畫穩定。
利用主鍵最大值定位分頁起點:
SELECT TOP @pageSize @select FROM @from
WHERE @key > (SELECT MAX(@key) FROM (SELECT TOP @pageSize*(@page-1) @key FROM @from ORDER BY @key) T)
ORDER BY @key;
缺點:需單一主鍵、主鍵需可排序、不支援複雜排序。
| 分頁方法 | 第1頁 | 第10頁 | 第100頁 | 第1000頁 | 第10000頁 | 第100000頁 |
|---|---|---|---|---|---|---|
| 二次 TOP | 4 | 7 | 404 | 28 | 271 | 3926 |
| ROW_NUMBER | 1 | 1 | 2 | 12 | 108 | 3594 |
| 通用分頁(MAX) | 1 | 1 | 1 | 10 | 82 | 3487 |
| DataReader游標 | 0 | 0 | 1 | 9 | 91 | 3380 |
WHERE Id > @lastSeenId ORDER BY Id LIMIT @pageSize,效能與頁碼無關。ROW_NUMBER、通用分頁、DataReader 表現接近,DataReader 略勝一籌且不佔用資料庫 TempDB。OFFSET @offset ROWS FETCH NEXT @pageSize ROWS ONLY,語法標準、最佳化器支援良好。