LIMIT offset, rows 在大偏移量時效能崩塌:MySQL 需掃描 offset+rows 行再丟棄 offset 行。本文提供三種優化方案,適用不同業務場景。
-- 10 萬頁,每頁 20 條 → 掃描 1,000,020 行
SELECT msg_id, msg_content FROM message
ORDER BY gmt_create DESC LIMIT 1000000, 20;
EXPLAIN 顯示 type=index 或 ALL,rows 巨大,Extra: Using filesort。
核心思想:先在索引上完成分頁(只讀主鍵/排序列),再回表查詳情。
-- 寫法 1:INNER JOIN 子查詢(MySQL 5.6+ 推薦)
SELECT m.msg_id, m.msg_content
FROM message m
INNER JOIN (
SELECT msg_id
FROM message
ORDER BY gmt_create DESC
LIMIT 1000000, 20
) t USING (msg_id);
-- 寫法 2:IN 子查詢(舊版本或偏好)
SELECT msg_id, msg_content
FROM message
WHERE msg_id IN (
SELECT msg_id
FROM message
ORDER BY gmt_create DESC
LIMIT 1000000, 20
);
type=index,Extra: Using index),極快。若知道查詢頁對應的時間窗口,直接用 BETWEEN 走索引範圍掃描。
SELECT msg_id, msg_content
FROM message
WHERE gmt_create BETWEEN '2023-01-01 00:00:00' AND '2023-01-31 23:59:59'
ORDER BY gmt_create DESC
LIMIT 20;
不使用 OFFSET,改用上一頁最後一條記錄的排序值作為下一頁起點。
-- 第 1 頁
SELECT msg_id, msg_content
FROM message
ORDER BY gmt_create DESC
LIMIT 20;
-- 記錄最後一條 gmt_create = '2023-05-01 12:00:00'
-- 第 2 頁
SELECT msg_id, msg_content
FROM message
WHERE gmt_create < '2023-05-01 12:00:00'
ORDER BY gmt_create DESC
LIMIT 20;
-- 第 N 頁:始終 WHERE gmt_create < 上頁最後值
WHERE (gmt_create, msg_id) < (last_gmt, last_id)(MySQL 5.7+ 支援元組比較)。| 方案 | 適用場景 | 優點 | 缺點 | 改造成本 |
|---|---|---|---|---|
| 延遲關聯 | 傳統分頁、跳頁需求、排序列有索引 | 兼容性好、不改業務邏輯、效能提升 10-100 倍 | 極深分頁仍有子查詢掃描成本 | 低(只改 SQL) |
| 顯式範圍 | 時間/數值範圍已知、報表查詢 | 最快、精確命中 | 需業務提供範圍參數 | 中(需調整參數傳遞) |
| 游標分頁 | 無限滾動、App 下拉刷新、API 分頁 | 效能恆定、無深分頁問題 | 不支援隨機跳頁、需前端配合 | 高(需改分頁協議) |
INDEX idx_gmt_create_msgid (gmt_create DESC, msg_id),子查詢直接滿足,無需回表。FORCE INDEX (idx_gmt_create) 防止優化器誤選。-- 建立測試表
CREATE TABLE message (
msg_id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
msg_content VARCHAR(500),
gmt_create DATETIME NOT NULL,
INDEX idx_gmt_create (gmt_create DESC)
) ENGINE=InnoDB;
-- 插入 200 萬測試數據(略)
-- 對比執行時間
SET profiling=1;
SELECT * FROM message ORDER BY gmt_create DESC LIMIT 1000000, 20; -- 原寫法
SELECT * FROM message INNER JOIN (SELECT msg_id FROM message ORDER BY gmt_create DESC LIMIT 1000000, 20) t USING(msg_id); -- 優化
SELECT * FROM message WHERE gmt_create < '2023-05-01 12:00:00' ORDER BY gmt_create DESC LIMIT 20; -- 游標
SHOW PROFILES;
總結:深分頁本質是「掃描大量無用行」。延遲關聯利用索引窄掃描+主鍵點查;範圍查詢利用業務維度縮小掃描;游標分頁從根源消除 OFFSET。根據業務形態選型,配合覆蓋索引,毫秒級響應輕鬆達標。