SELECT * FROM t ORDER BY id LIMIT 10 OFFSET 1000000 要求資料庫掃描並丟棄前 1,000,000 筆,I/O 與 CPU 隨偏移量線性增長。
利用唯一、有序、可索引的欄位(通常是主鍵或 created_at, id 複合鍵)記住上一頁最後一筆值。
-- 第一頁
SELECT * FROM t ORDER BY id LIMIT 20;
-- 後續頁(假設上一頁最後 id = 9527)
SELECT * FROM t WHERE id > 9527 ORDER BY id LIMIT 20;
優點:O(1) 索引搜尋,不受頁數影響。缺點:無法直接跳到任意頁(適合無限捲動、API 分頁)。
先只查主鍵,再回表取明細:
SELECT t.* FROM t
INNER JOIN (
SELECT id FROM t ORDER BY id LIMIT 10 OFFSET 1000000
) AS tmp USING (id)
ORDER BY t.id;
減少回表行數,但仍需掃描 OFFSET 行索引。
WITH numbered AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY id) AS rn FROM t
)
SELECT * FROM numbered WHERE rn BETWEEN 1000001 AND 1000020;
可讀性佳,但執行計畫通常仍會全掃描窗口函數,深分頁未必比 OFFSET 快。
SELECT * FROM dbo.t
ORDER BY id
OFFSET 1000000 ROWS FETCH NEXT 20 ROWS ONLY
OPTION (USE HINT('FORCE_LEGACY_CARDINALITY_ESTIMATION'));
SQL Server 2012+ 支援標準語法,配合覆蓋索引可接受中等偏移。
熱門列表(首頁、熱門標籤)預先計算每頁 ID 清單存入 Redis,查詢時直接 WHERE id IN (...) 保序取回。
| 方案 | 第 1 頁 | 第 50,000 頁 | 適用場景 |
|---|---|---|---|
| OFFSET | 2 ms | 1,200 ms | 極淺分頁、管理後台 |
| Keyset (id > ?) | 2 ms | 2 ms | 無限捲動、API、日誌查詢 |
| 延遲關聯 | 3 ms | 180 ms | 需跳頁、欄位較多 |
| Redis 頁碼映射 | 1 ms | 1 ms | 熱門靜態列表 |
next_cursor)。ORDER BY 欄位 + WHERE 篩選欄位。SELECT *,只取需要欄位減少回表。Slow_queries 設定 long_query_time=1 捕捉深分頁慢查詢。選對分頁策略,百萬級資料也能毫秒級響應。