分頁是業務高頻場景,但 LIMIT offset, size 隨著 offset 增大效能急劇下降。本文對比「電梯式」(精確跳頁)與「扶梯式」(游標/Keyset)分頁,深度剖析優化策略與 ORDER BY 索引陷阱。
| 維度 | 電梯式 | 扶梯式 |
|---|---|---|
| SQL 模式 | LIMIT (page-1)*size, size | WHERE id > last_id ORDER BY id LIMIT size |
| 用戶體驗 | 可跳轉任意頁、顯示總頁數 | 僅下一頁/上一頁、無總數 |
| 效能特徵 | Offset 大時掃描大量棄行 | 固定掃描 size 行,極穩定 |
| 適用場景 | 後台管理、搜尋結果、需跳頁 | Feed 流、無限滾動、App 列表 |
-- 兩條 SQL 模式
SELECT COUNT(*) FROM t WHERE x=y; -- 總數
SELECT * FROM t WHERE x=y ORDER BY id LIMIT 100000, 10; -- 第 10001 頁
LIMIT 100000, 10 需讀取 100,010 行 → 棄讀 100,000 行 → 極慢。COUNT(*) 全表掃描或二級索引掃描,同樣昂貴。-- 首頁
SELECT * FROM t WHERE x=y ORDER BY id LIMIT 10;
-- 翻頁:前端傳 last_id
SELECT * FROM t WHERE x=y AND id > 100000 ORDER BY id LIMIT 10;
(x, id) 直接定位,無回表、無 Filesort。-- 原始:全字段回表排序
SELECT * FROM pa_dc_flow ORDER BY subject_code DESC LIMIT 100000, 5;
-- 優化:子查詢只取主鍵排序,再 JOIN 回表
SELECT t.* FROM pa_dc_flow t
INNER JOIN (
SELECT id FROM pa_dc_flow ORDER BY subject_code DESC LIMIT 100000, 5
) tmp USING (id);
SELECT id ... ORDER BY subject_code 可走 subject_code 索引(覆蓋索引),僅掃描索引葉子節點。id 精準回表 5 行,極快。| SQL 模式 | 能否用索引排序 | 原因 |
|---|---|---|
SELECT * FROM t ORDER BY subject_code | ❌ | 需回表,優化器選全表掃描+Filesort |
SELECT id FROM t ORDER BY subject_code | ✅ | 覆蓋索引,僅掃描索引 |
SELECT subject_code FROM t ORDER BY subject_code | ✅ | 覆蓋索引 |
SELECT * FROM t WHERE subject_code=X ORDER BY subject_code | ✅ | WHERE 條件與 ORDER BY 同欄,複合索引滿足最左前綴 |
核心:ORDER BY 欄位若要走索引排序,必須滿足:
① 出現在 SELECT 列表(覆蓋索引),或
② 出現在 WHERE 條件,或
③ 與 WHERE 條件欄位建立複合索引 (where_col, order_col) 滿足最左前綴。
EXPLAIN SELECT COUNT(*) FROM t WHERE x=y;
-- rows 欄位為優化器估算行數,無需真實掃描,極快
-- 適用:不需精確總數、僅做分頁條顯示「約 N 條」
LIMIT offset, size 處理百萬級資料深翻頁。ORDER BY 非索引欄位 + 大 Offset = 雙重災難。(WHERE欄位, ORDER BY欄位, SELECT欄位) 覆蓋查詢。ANALYZE TABLE 更新統計,避免優化器誤判。