MySQL 的性能極限在哪裡?許多開發者在處理小型系統(萬級數據)時,隨便寫寫 SQL、套個框架就能跑得飛快。但當數據量衝擊到 10 萬、百萬甚至千萬級時,一條看似無害的 LIMIT offset, N 就能拖垮整個系統。筆者近期經歷了從痛苦、絕望到重拾信心的完整過程,決定用實測數據說話,帶大家徹底搞懂大數據分頁優化的核心邏輯。
測試表 collect (id, title, info, vtype),其中 id 為主鍵遞增,vtype 為 tinyint 並建立索引。填入 10 萬條記錄,表大小 1.6 GB。
SELECT id, title FROM collect LIMIT 1000, 10; → 0.01 秒,極快。SELECT id, title FROM collect LIMIT 90000, 10; → 8-9 秒,災難級延遲。同樣是掃描 10 條記錄,為什麼偏移量大就慢成這樣?MySQL 的 LIMIT offset, N 實際上會先掃描並丟棄前 offset 行,再取 N 行。偏移量越大,丟棄的行數越多,I/O 開銷呈線性增長。
SELECT id FROM collect ORDER BY id LIMIT 90000, 10;
結果:0.04 秒。因為只讀取主鍵索引(覆蓋索引),不回表,速度飛快。網上常見改寫方案:
SELECT id, title FROM collect
WHERE id >= (SELECT id FROM collect ORDER BY id LIMIT 90000, 1)
LIMIT 10;
此法利用主鍵定位起始 ID,再回表取 10 行,確實快。但問題來了——加上 WHERE vtype=1 後:
SELECT id FROM collect WHERE vtype=1 ORDER BY id LIMIT 90000, 10;
依然 8-9 秒!明明 vtype 有索引,為何失效?
建立索引表 t(id, title, vtype) 定長存儲,分頁查 t 再回 collect 取 info。10 萬數據時 t 表僅 20 MB,查詢 0.1-0.2 秒。但擴充到 100 萬(t 表 200+ MB),LIMIT 900000, 10 又飆升到 1-2 秒。分表並未根治問題,本質仍是全索引掃描,只是數據量小時快而已。
偶然發現:建立複合索引 search(vtype, id),WHERE 條件列在前,排序/分頁用的主鍵在後,且 只 SELECT 主鍵:
SELECT id FROM collect WHERE vtype=1 LIMIT 90000, 10;
驚人地快——0.04 秒!但一旦 SELECT id, title 就又回到 8-9 秒,因為觸發回表,不再走覆蓋索引。
核心結論:帶 WHERE 且需 LIMIT 分頁時,必須建立 (where_column, order_by_column) 複合索引,並僅查主鍵。
既然只能快速取 ID,那麼先查 ID 列表,再用 IN 批量回表:
// 步驟 1:分頁查主鍵(走覆蓋索引,極快)
$sql = "SELECT id FROM collect WHERE vtype=$vtype";
$db->pagesize = 20;
$db->execute($sql);
$strpage = $db->strpage(); // 保存分頁字符串
// 步驟 2:構造 ID 字符串,批量回表取完整字段
while ($rs = $db->fetch_array()) {
$strid .= $rs['id'] . ',';
}
$strid = substr($strid, 0, -1);
$db->pagesize = 0; // 關鍵:清空分頁,復用連接
$db->execute("SELECT id, title, url, sTime, gTime, vtype, tag
FROM collect WHERE id IN ($strid)");
// 輸出模板渲染...
IN 列表同樣能命中主鍵索引,單次查詢 0 秒級。這招不需要分表、不改框架分頁邏輯,百萬級分頁穩定在 0.0x 秒。
LIMIT offset, N 深分頁本質是全掃描,offset 越大越慢。WHERE 會失效。IN 回表)工程落地成本最低,效果最佳。MySQL 優化,索引設計是靈魂。掌握這套組合拳,千萬級分頁不再是夢。