傳統 LIMIT offset, rows 在大偏移量時效能急劇下降,因 MySQL 需掃描並丟棄前 offset 行。優化思路:先定位主鍵 ID 範圍,再回表查試完整欄位。
適用於 MySQL 8.0+(支援窗口函數)。測試腳本:
-- 建立測試庫表
CREATE DATABASE TestForPaging;
USE TestForPaging;
CREATE TABLE SomeData (
id INT PRIMARY KEY,
name VARCHAR(30),
description TEXT
);
-- 插入樣本資料
INSERT INTO SomeData VALUES
(1,'num1','第1條'), (2,'num2','第2條'), (3,'num3','第3條'),
(4,'num4','第4條'), (5,'num5','第5條');
-- 總筆數
SELECT COUNT(*) FROM SomeData;
-- 為每行生成排序後的行號
SELECT name, description, ROW_NUMBER() OVER (ORDER BY id DESC) AS dataLevel
FROM SomeData;
-- 查詢指定頁面(如第 2-4 條)
SELECT dataLevel, name, description FROM (
SELECT name, description, ROW_NUMBER() OVER (ORDER BY id DESC) AS dataLevel
FROM SomeData
) AS t
WHERE dataLevel BETWEEN 2 AND 4;
-- 封裝為存儲過程
CREATE PROCEDURE GetDataPaged (
@startRowIndex INT,
@maximumRows INT,
@sort VARCHAR(100)
)
BEGIN
IF LENGTH(@sort) = 0 THEN SET @sort = 'id'; END IF;
SELECT dataLevel, name, description FROM (
SELECT name, description, ROW_NUMBER() OVER (ORDER BY @sort DESC) AS dataLevel
FROM SomeData
) AS t
WHERE dataLevel > (@startRowIndex * 10)
AND dataLevel <= (@startRowIndex * 10 + @maximumRows);
END;
ROW_NUMBER() OVER (ORDER BY ...) 為結果集生成連續行號JOIN 回表-- 方案 1:子查詢鎖定 ID 範圍 SELECT * FROM SomeData WHERE id >= (SELECT id FROM SomeData ORDER BY id LIMIT 100000, 1) ORDER BY id LIMIT 20; -- 方案 2:使用變數模擬 ROW_NUMBER() SET @rownum = 0; SELECT * FROM ( SELECT @rownum := @rownum + 1 AS rownum, t.* FROM SomeData t ORDER BY id ) AS t2 WHERE rownum BETWEEN 100001 AND 100020;
| 方案 | 偏移 10 萬耗時 | 適用版本 | 備註 |
|---|---|---|---|
| LIMIT 100000, 20 | ~1.2 秒 | 所有版本 | 全掃描前 10 萬行 |
| ROW_NUMBER() 窗口函數 | ~0.03 秒 | 8.0+ | 優化器可下推篩選 |
| 子查詢定位 ID | ~0.05 秒 | 5.6+ | 需主鍵有序且無大量刪除空洞 |
| 變數模擬行號 | ~0.4 秒 | 所有版本 | 需全表掃描生成行號 |
ROW_NUMBER() 窗口函數,語法清晰且效能最佳LIMIT offset, rows掌握分頁優化技巧,能讓高併發、大數據量的列表查詢保持毫秒級響應。