大數據量下 LIMIT M, N 深分頁效能急劇下降 (掃描 M+N 行丟棄 M 行)。本文對比 8 種方案原理、適用場景、優缺點,附 MySQL 8.0+ 現代最佳實踐。
| 方案 | 核心思路 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|---|
| 1. 直接 LIMIT | 全表掃描 + 丟棄 | 數據量小 (<10萬) | 簡單 | 深分頁極慢、結果集不穩定 |
| 2. 主鍵過濾 | WHERE id > offset LIMIT N | ID 連續、單表 | 索引範圍掃描、快 | ID 不連續會漏數據、需有序 |
| 3. 索引 + ORDER BY | WHERE id > offset ORDER BY id LIMIT N | ID 不連續、需穩定序 | 利用索引排序、結果穩定 | MySQL 僅支援 ASC 索引掃描 |
| 4. PreparedStatement | 預編譯 + 參數化 | 高併發重複分頁 | 減少解析開銷、防注入 | 本質仍是方案 3、邊界同樣 |
| 5. 覆蓋索引定位 | WHERE pk >= start_id ORDER BY pk LIMIT N | 知曉起始 ID (游標分頁) | 純索引掃描、極快、無 OFFSET | 不支援任意頁跳轉、需記錄上頁最大 ID |
| 6. 子查詢/連接回表 | 先查主鍵再 JOIN 回表 | 深分頁、需任意頁跳轉 | 減少回表次數、利用索引 | SQL 複雜、臨時表開銷 |
| 7. 存儲過程 | 封裝邏輯在服務端 | 極複雜分頁邏輯 | 減少網絡交互 | 難維護、業務邏輯下沉、不推薦 |
| 8. SQL_CALC_FOUND_ROWS | 掃全表計總數 | ❌ 不推薦 | 無 | 全表掃描、5.7 已棄用、8.0 移除 |
原理: 記住上一頁最大主鍵 last_id,下一頁查 WHERE id > last_id ORDER BY id LIMIT 20。
-- 首頁
SELECT * FROM articles ORDER BY id DESC LIMIT 20;
-- 翻頁 (前端傳遞 last_id=1000)
SELECT * FROM articles WHERE id < 1000 ORDER BY id DESC LIMIT 20;
優勢: O(N) 複雜度、無 OFFSET 掃描、利用主鍵索引、高併發穩定。
限制: 不支援「跳到第 100 頁」、不支援雙向翻頁 (需記錄 first_id)、排序欄位必須唯一 (主鍵或唯一索引)。
需任意頁跳轉時,先在索引上定位主鍵,再回表取完整列:
-- 子查詢版
SELECT * FROM t1
WHERE id <= (SELECT id FROM t1 ORDER BY id DESC LIMIT 10000, 1)
ORDER BY id DESC LIMIT 20;
-- JOIN 版 (通常更快)
SELECT t1.* FROM t1
JOIN (SELECT id FROM t1 ORDER BY id DESC LIMIT 10000, 1) t2
ON t1.id <= t2.id
ORDER BY t1.id DESC LIMIT 20;
原理: 內層僅掃描主鍵索引 (覆蓋索引),外層按範圍回表,避免 LIMIT 10000, 20 掃描 10020 行聚簇索引。
next_cursor,前端無感知翻頁,效能最優。LIMIT offset, size 但限制 offset < 10000,超過引導用戶用搜尋/篩選縮小範圍。INDEX idx_list (status, created_at, id) 使 WHERE status=1 ORDER BY created_at DESC, id DESC LIMIT 20 純索引完成。long_query_time=1 捕獲深分頁慢 SQL,EXPLAIN 分析 type=index vs ALL。總結: 深分頁本質是「跳過前 N 行」的代價。**游標分頁** 從業務交互層面消除 OFFSET,是根治方案;配合**覆蓋索引**、**讀寫分離**、**分庫分表**,可支撐億級數據分頁。