萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 資料庫 >> 資料庫綜合

MySQL / SQL Server 高效分頁查詢:避免 OFFSET 深分頁效能陷阱

為什麼 OFFSET 分頁會變慢?

SELECT * FROM t ORDER BY id LIMIT 10 OFFSET 1000000 要求資料庫掃描並丟棄前 1,000,000 筆,I/O 與 CPU 隨偏移量線性增長。

方案一:游標/Keyset 分頁(推薦)

利用唯一、有序、可索引的欄位(通常是主鍵或 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 行索引。

方案三:MySQL 8.0+ CTE + ROW_NUMBER()(標準 SQL)

WITH numbered AS (
  SELECT *, ROW_NUMBER() OVER (ORDER BY id) AS rn FROM t
)
SELECT * FROM numbered WHERE rn BETWEEN 1000001 AND 1000020;

可讀性佳,但執行計畫通常仍會全掃描窗口函數,深分頁未必比 OFFSET 快。

方案四:SQL Server 專用:OFFSET-FETCH + 索引提示

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 頁適用場景
OFFSET2 ms1,200 ms極淺分頁、管理後台
Keyset (id > ?)2 ms2 ms無限捲動、API、日誌查詢
延遲關聯3 ms180 ms需跳頁、欄位較多
Redis 頁碼映射1 ms1 ms熱門靜態列表

最佳實踐清單

  • ✅ API 介面優先採用 Keyset 分頁(回傳 next_cursor)。
  • ✅ 前端「跳到第 N 頁」改為「載入更多」或提供搜尋/篩選替代。
  • ✅ 索引必須覆蓋 ORDER BY 欄位 + WHERE 篩選欄位。
  • ✅ 避免 SELECT *,只取需要欄位減少回表。
  • ✅ 監控 Slow_queries 設定 long_query_time=1 捕捉深分頁慢查詢。

選對分頁策略,百萬級資料也能毫秒級響應。

copyright © 萬盛學電腦網 all rights reserved