LIMIT offset, length 會掃描前 offset + length 行,再丟棄前 offset 行。當 offset 達百萬級,掃描龐大中間結果集導致極慢甚至阻塞。
-- 例:500萬資料
LIMIT 0, 10 -- 掃 10 行,極快
LIMIT 200000, 10 -- 掃 200,010 行,明顯變慢
LIMIT 450000, 30 -- 掃 450,030 行,可能堵死
先只查主鍵 ID (覆蓋索引,極快),再回表關聯完整欄位。
-- 原始慢查詢
SELECT * FROM wl_tagindex WHERE byname='f' ORDER BY id LIMIT 300000, 10;
-- 優化:子查詢只走索引
SELECT * FROM (
SELECT id FROM wl_tagindex WHERE byname='f' ORDER BY id LIMIT 300000, 10
) a LEFT JOIN wl_tagindex b ON a.id = b.id;
-- 執行時間:3.21s → 0.11s
前提: (byname, id) 建立**複合索引**,確保子查詢為「覆蓋索引」純索引掃描。
適用於「下一頁」場景,不支援任意跳頁。
-- 首頁
SELECT id, name, content FROM users ORDER BY id ASC LIMIT 20;
-- 記錄最後一筆 id = 100073,下一頁
SELECT id, name, content FROM users WHERE id > 100073 ORDER BY id ASC LIMIT 20;
-- 僅掃描 20 行
優點: 無 offset 掃描,效能恆定。缺點: 不支援跳轉指定頁碼、ID 需連續遞增 (自增主鍵最佳)。
當頁碼超過總頁數一半時,改用 DESC 從尾部倒數。
-- 總記錄 1,628,775,每頁 40,共 40,720 頁
-- 第 30,000 頁 (正向 offset=1,199,960)
SELECT * FROM abc WHERE BatchID=123 LIMIT 1199960, 40; -- 2.65s
-- 反向:第 30,000 頁 = 倒數 10,720 頁
SELECT * FROM abc WHERE BatchID=123 ORDER BY InputDate DESC LIMIT 428775, 40; -- 1.00s
注意: 結果為降序,需應用層反轉。需知道總記錄數、建立排序欄位索引。
阿里巴巴等大廠做法:offset > 5000 直接返回空或引導用戶用搜尋/篩選。
理由: 用戶極少翻超過 100 頁,深分頁多為爬蟲或異常流量,保護資源優於體驗。
若表含 TEXT/BLOB 大欄位,傳輸量大時適用:
SELECT id, title, content
FROM items
WHERE id IN (SELECT id FROM items ORDER BY id LIMIT 900000, 10);
內層只掃描主鍵索引,外層按主鍵回表 10 行。
| 方案 | 適用場景 | 優點 | 缺點 | 實測提升 |
|---|---|---|---|---|
| 延遲關聯/子查詢 | 通用分頁、支援跳頁 | 無需業務改造、相容性好 | 需覆蓋索引、仍掃描 offset 索引行 | 3.21s → 0.11s (30x) |
| 游標分頁 (ID>) | 無限滾動、下一頁、App 列表 | 效能恆定 O(1)、最快 | 不支援任意跳頁、需連續 ID | 100,000 offset → 0.02s (5000x) |
| 反向查找 | 大頁碼 (>總頁數/2) | 大幅減少掃描行 | 需總數、結果倒序、索引維護成本 | 2.65s → 1.00s (2.6x) |
| 限制翻頁深度 | 高併發、防爬蟲 | 根治資源耗盡 | 犧牲極少數用戶體驗 | 業務層面解決 |
| IN 子查詢 | 含大字段表 | 減少大字段傳輸 | 優化器可能轉為 JOIN 失效 | 視表結構而定 |
long_query_time=1 專門捕獲深分頁 SQL深分頁優化無銀彈,**業務場景決定技術選型**。理解 LIMIT 掃描機制,結合索引設計與業務妥協,才能在效能與體驗間找到平衡點。