LIMIT 1000000, 10 是經典效能殺手:MySQL 必須掃描前 100 萬行丟棄,再取 10 行。隨著偏移量增大,響應時間線性增長。本文對比三種分頁模式,給出「延遲關聯」與「游標式 (Keyset Pagination)」兩大優化方案,實現百萬級數據毫秒級翻頁。
-- 第 100001 頁,每頁 10 條 SELECT * FROM orders WHERE status=1 ORDER BY id DESC LIMIT 1000000, 10;
執行流程:
status=1 過濾 (或全表掃描)。id DESC 排序 (可能 filesort)。偏移量越大,丟棄工作越多,QPS 急劇下降。
核心思想:子查詢只走覆蓋索引取主鍵,再回表取完整列,避免大量無用列參與排序與傳輸。
-- 優化前:全列掃描排序
SELECT * FROM orders WHERE status=1 ORDER BY id DESC LIMIT 1000000, 10;
-- 優化後:子查詢只取 id (覆蓋索引 idx_status_id)
SELECT o.*
FROM orders o
JOIN (
SELECT id
FROM orders
WHERE status=1
ORDER BY id DESC
LIMIT 1000000, 10
) AS derived USING (id);
效能提升點:
SELECT id 走 idx_status_id (status, id) 覆蓋索引,無回表、無 filesort (索引已有序)。JOIN ... USING (id) 只回表 10 次 (主鍵精確查找)。核心思想:不使用 OFFSET,改用「上一頁最後一條記錄的排序鍵值」作為下一頁起點。
-- 首頁 SELECT * FROM orders WHERE status=1 ORDER BY id DESC LIMIT 10; -- 記錄最後一條 id = 999990 -- 下一頁 SELECT * FROM orders WHERE status=1 AND id < 999990 ORDER BY id DESC LIMIT 10; -- 再下一頁 (假設最後 id = 999980) SELECT * FROM orders WHERE status=1 AND id < 999980 ORDER BY id DESC LIMIT 10;
優勢:
range 索引查找 + LIMIT,時間複雜度 O(log N + K),極其穩定。適用限制:
(sort_col, pk) 組合游標。建立最優索引支撐上述兩種方案:
方案 SQL 模式 執行時間 適用場景 原生 OFFSET LIMIT 1000000, 10~2.5 秒 僅極小偏移 (<1000) 延遲關聯 子查詢取 id + JOIN 回表 ~20 毫秒 需隨機跳頁、兼容舊版 游標式 WHERE id < last_id LIMIT 10~3 毫秒 無限滾動、列表流、API 分頁 總數查詢優化 (COUNT(*))
分頁組件常需總記錄數:
SELECT COUNT(*) FROM orders WHERE status=1 (走覆蓋索引 idx_status_id 快速掃描)。EXPLAIN SELECT * FROM orders WHERE status=1 觀察 rows 欄位,或 SHOW TABLE STATUS LIKE 'orders' 的 Rows (非事務安全但極快)。count:orders:status=1,異步更新 (定時任務/觸發器)。(where_col, order_col) 複合索引。ORDER BY status, created_at DESC 游標需雙值 (status, created_at),SQL 改 WHERE (status, created_at) < (last_status, last_created_at) (元組比較 8.0+ 支援)。MySQL 分頁優化鐵律:**拒絕大偏移 OFFSET**。
• 需隨機跳頁 → **延遲關聯 (子查詢覆蓋索引取主鍵 + JOIN 回表)**。
• 僅順序翻頁/無限滾動 → **游標式分頁 (Keyset Pagination)**,效能極致穩定。
配合覆蓋索引、ICP、總數緩存,構建百萬級數據毫秒級分頁體驗。