表 collect (100 萬行,1.6GB),查詢:
SELECT id, title FROM collect LIMIT 900000, 10; -- 8-9 秒
同表查詢 LIMIT 1000, 10 僅 0.01 秒。為什麼深分頁性能斷崖式下跌?
MySQL 執行 LIMIT offset, n 時:
Offset 越大,丟棄的行越多,IO 與 CPU 線性增長。LIMIT 900000, 10 實際掃描 900,010 行。
-- 利用主鍵聚簇索引順序,子查詢只掃描主鍵值
SELECT id, title FROM collect
WHERE id >= (SELECT id FROM collect ORDER BY id LIMIT 900000, 1)
LIMIT 10;
SELECT id ... LIMIT 900000, 1 走主鍵索引,只讀 8B × 90萬 = 7.2MB,極快WHERE id >= ... LIMIT 10 範圍查詢,命中主鍵索引建立聯合索引 (where_column, pk_column):
ALTER TABLE collect ADD INDEX idx_vtype_id (vtype, id);
查詢:
-- 只查主鍵,走覆蓋索引,不回表
SELECT id FROM collect WHERE vtype = 1 ORDER BY id LIMIT 900000, 10;
-- 0.04 秒!
核心規律:WHERE 條件列在前 + ORDER BY 主鍵在後 的聯合索引,
只 SELECT 主鍵 時走覆蓋索引,深分頁極快。
一旦 SELECT * 或查詢非索引列,優化器放棄覆蓋索引轉全表掃描。
需返回完整列時:
-- 1. 先用覆蓋索引取主鍵
SELECT id FROM collect WHERE vtype = 1 ORDER BY id LIMIT 900000, 10;
-- 2. 再用主鍵 IN 查詢完整數據 (主鍵點查極快)
SELECT * FROM collect WHERE id IN (900001, 900002, ..., 900010);
或單條 SQL:
SELECT * FROM collect
WHERE id IN (
SELECT id FROM collect WHERE vtype = 1 ORDER BY id LIMIT 900000, 10
);
MySQL 5.6+ 優化器將 IN 子查詢轉為 EXISTS 半連接,主鍵查詢極快。
不使用 OFFSET,改用「上一頁最大 ID」:
-- 第一頁
SELECT * FROM collect WHERE vtype = 1 ORDER BY id LIMIT 20;
-- 後續頁 (假設上一頁最大 id = 900000)
SELECT * FROM collect WHERE vtype = 1 AND id > 900000 ORDER BY id LIMIT 20;
| 方案 | SQL 模式 | 耗時 | 適用場景 |
|---|---|---|---|
| 原始 | LIMIT 900000, 10 | 8.5 秒 | 不可用 |
| 主鍵子查詢 | WHERE id >= (SELECT id ... LIMIT 900000,1) | 0.05 秒 | 主鍵連續、無 WHERE 條件 |
| 聯合索引覆蓋 | INDEX(vtype,id) + SELECT id | 0.04 秒 | 有 WHERE、只需 ID |
| 延遲關聯 | SELECT * WHERE id IN (SELECT id ...) | 0.06 秒 | 需完整列、通用最優 |
| 游標分頁 | WHERE id > last_id LIMIT 20 | 0.01 秒 | API 無限滾動、無跳頁需求 |
-- 深分頁查詢模式:
SELECT [columns] FROM table
WHERE [where_conditions]
ORDER BY [pk_column]
LIMIT [offset], [size];
-- 最優索引:
CREATE INDEX idx_where_pk ON table (where_column, pk_column);
-- 覆蓋索引條件:SELECT 列 ⊆ 索引列 (where_column, pk_column)
-- 僅 SELECT pk_column 時 100% 走覆蓋索引
(id, vtype) 完全無效。FORCE INDEX (idx_vtype_id) 或重寫查詢。setFirstResult(900000),改用 WHERE id > :lastId 或 Native QueryPageHelper.optimizeOffset() 自動改寫為延遲關聯深分頁優化本質是將隨機 IO 轉為順序索引掃描 + 主鍵點查。掌握聯合索引覆蓋與延遲關聯模式,千萬級數據也能毫秒級翻頁。