MySQL LIMIT offset, rows 在淺分頁(offset 小)表現良好,但百萬級數據深分頁(如 LIMIT 500000, 10)會導致全表掃描前 50 萬行再丟棄,性能急劇下降。本文對比多種優化方案。
SELECT * FROM tbl LIMIT offset, rows;
-- 等價於
SELECT * FROM tbl LIMIT rows OFFSET offset;
LIMIT 490000, 10 需掃描 49 萬行索引/數據頁,丟棄前 49 萬行,僅返回 10 行。隨 offset 增大,耗時線性增長。
SELECT * FROM news
WHERE id >= (SELECT id FROM news ORDER BY id LIMIT 490000, 1)
ORDER BY id LIMIT 10;
原理:子查詢僅遍歷主鍵索引定位到目標 ID(Using index),外層查詢直接範圍掃描 10 行,避免大量回表與丟棄。實測 0.18s vs 0.22s,優勢明顯。
適用於「下一頁」場景,前端傳遞上一頁最後一條 id:
SELECT * FROM news WHERE id > last_seen_id ORDER BY id LIMIT 10;
無 offset,極快。但不支持隨機跳頁。
SELECT t.* FROM tbl t
JOIN (SELECT id FROM tbl ORDER BY id LIMIT 490000, 10) tmp USING (id);
子查詢僅掃描索引(覆蓋索引),外層通過主鍵回表 10 行,減少回表次數。
| 方案 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
| 子查詢定位 ID | 隨機跳頁、主鍵連續 | 兼容性好、無需改動業務邏輯 | 需主鍵有序且無大量空洞 |
| 游標式 | 無限滾動、順序翻頁 | 性能最穩定、無深分頁問題 | 不支持隨機跳轉 |
| 延遲關聯 | 複雜查詢、需聯表 | 減少回表、利用覆蓋索引 | SQL 較複雜 |
ORDER BY 列有索引(最好是主鍵)。SELECT *,只取所需列,增加覆蓋索引命中率。核心思想:用索引定位起點,而非掃描丟棄。