MySQL SELECT 查詢語法 ── LIMIT 分頁查詢
簡介
在日常的資料庫開發中,分頁顯示是最常見的需求之一。無論是網站的文章列表、商品目錄,或是管理後台的資料表格,都需要一次只取出部份資料,讓使用者可以「上一頁/下一頁」地瀏覽。若直接把整個結果集一次取回,除了浪費記憶體外,還會嚴重拖慢系統效能。
MySQL 提供的 LIMIT 子句正是為了解決這個問題而設計的。它能夠在 SELECT 語句中限定回傳的列數,搭配 OFFSET(或直接使用 LIMIT offset, row_count)即可輕鬆實作分頁。掌握 LIMIT 的使用方式與背後的執行原理,是每位 MySQL 開發者必備的基礎功。
本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,逐步帶你建立穩定且效能良好的分頁查詢。
核心概念
1. LIMIT 的基本語法
SELECT 欄位列表
FROM 資料表
WHERE 條件
ORDER BY 排序欄位
LIMIT [offset,] row_count;
- row_count:本次查詢欲回傳的最大列數。
- offset(可選):要跳過的列數,常用於「第 N 頁」的計算。
- 若只寫
LIMIT row_count,表示從第一筆開始取row_count筆。 - 若寫
LIMIT offset, row_count,則先跳過offset筆,再取row_count筆。
小技巧:
OFFSET n也是合法寫法,等同於LIMIT n, row_count,但在 MySQL 中較少使用。
2. 分頁公式
假設每頁顯示 10 筆,第 p 頁的起始列(offset)可用下列公式計算:
offset = (p - 1) * 10
範例:第 3 頁 → offset = (3-1) * 10 = 20,意即「跳過前 20 筆,取 10 筆」。
3. 為什麼要配合 ORDER BY
LIMIT 本身不保證資料的順序。若不加 ORDER BY,每次查詢得到的資料可能會因為底層索引或資料寫入順序的變化而不同,導致分頁結果不一致。務必在使用 LIMIT 前先設定明確的排序規則。
4. 使用索引提升分頁效能
分頁查詢若必須掃描大量資料會造成效能瓶頸。以下幾點可以協助 MySQL 利用索引加速:
| 方法 | 說明 |
|---|---|
| 索引排序欄位 | 把 ORDER BY 使用的欄位建成索引,讓 MySQL 能直接在索引上定位。 |
| 覆蓋索引 (Covering Index) | 若 SELECT 的欄位全都在同一個索引中,MySQL 只會讀索引,不必回表。 |
| 使用「鍵值分頁」 | 針對大量資料,可改用 WHERE 主鍵 > 上一頁最後一筆的主鍵 方式,避免大 OFFSET 的成本。 |
5. 實務上常見的分頁寫法
範例 1:最簡單的前 10 筆
SELECT id, title, created_at
FROM articles
ORDER BY created_at DESC
LIMIT 10;
取得最新的 10 篇文章。
範例 2:取得第 2 頁(每頁 15 筆)
SELECT id, name, price
FROM products
WHERE status = 'active'
ORDER BY price ASC
LIMIT 15 OFFSET 15; -- 或寫成 LIMIT 15, 15
OFFSET 15表示跳過前 15 筆(第 1 頁),再取 15 筆(第 2 頁)。
範例 3:使用子查詢搭配 LIMIT 取得分頁總筆數
SELECT SQL_CALC_FOUND_ROWS *
FROM users
WHERE role = 'member'
ORDER BY signup_date DESC
LIMIT 20, 20; -- 第 2 頁,每頁 20 筆
SELECT FOUND_ROWS() AS total_rows; -- 取得不加 LIMIT 時的總筆數
SQL_CALC_FOUND_ROWS讓 MySQL 在同一次查詢中同時計算總筆數,避免再寫一次COUNT(*)。
範例 4:鍵值分頁(避免大 OFFSET)
-- 先取得上一頁最後一筆的 id(假設為 1050)
SELECT id, title, created_at
FROM articles
WHERE id > 1050
ORDER BY id ASC
LIMIT 10;
只要知道上一頁的最後一筆主鍵,就可以用
> 上一筆 id直接定位,效能遠高於OFFSET。
範例 5:結合子查詢與 LIMIT 取出分頁和總筆數
SELECT *
FROM (
SELECT id, name, score
FROM exam_results
WHERE exam_id = 3
ORDER BY score DESC
LIMIT 0, 25
) AS page
UNION ALL
SELECT COUNT(*) AS total_rows, NULL AS id, NULL AS name, NULL AS score
FROM exam_results
WHERE exam_id = 3;
這個技巧一次返回分頁資料與總筆數,適合 API 一次回傳兩項資訊。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
| 忘記 ORDER BY | 會導致分頁結果不穩定,使用者翻頁時資料可能重複或遺失。 | 必須在每次使用 LIMIT 前明確指定排序欄位,且排序欄位最好有索引。 |
| 大 OFFSET | OFFSET 需要 MySQL 先掃過前面的所有列,當頁數變大時效能急遽下降。 |
盡量使用 鍵值分頁(> 上一筆主鍵)或 分段查詢(使用 WHERE 條件限制範圍)。 |
| COUNT(*) 與 LIMIT 同時使用 | 直接寫 SELECT COUNT(*) LIMIT 10 只會回傳 10,錯誤的總筆數計算。 |
使用 SQL_CALC_FOUND_ROWS 或另寫一次 SELECT COUNT(*) FROM ...。 |
| SELECT * 搭配 LIMIT | 若表格欄位很多且未使用覆蓋索引,會造成不必要的 I/O。 | 只取需要的欄位,或建立覆蓋索引。 |
| 忽略資料變動 | 分頁期間資料被新增或刪除,可能導致頁面跳過或重複。 | 若需求對一致性要求高,可在查詢時加上 快照讀(READ COMMITTED)或使用 鍵值分頁。 |
推薦的最佳實踐
- 永遠加 ORDER BY,且排序欄位應有適當索引。
- 避免過大的 OFFSET,改用鍵值分頁或分段查詢。
- 只取必要欄位,減少傳輸與 I/O。
- 使用預先計算的總筆數(如
SQL_CALC_FOUND_ROWS)或快取計數結果,減少重複計算。 - 在 API 設計上,將分頁參數(page、pageSize)明確化,並回傳
total、page、pageSize等資訊,方便前端渲染。
實際應用場景
1. 電子商務商品列表
在商品搜尋結果頁面,每次顯示 20 件商品。使用 LIMIT 搭配 ORDER BY price ASC,同時把 category_id、stock_status 等條件放在 WHERE 中,確保搜尋速度。若商品數量超過 10,000 件,則改用 鍵值分頁(WHERE product_id > last_id)避免大量 OFFSET。
2. 社交平台動態牆
使用者的動態牆需要即時載入新貼文,同時支援無限捲動。前端送來 last_post_id,後端執行:
SELECT id, user_id, content, created_at
FROM posts
WHERE id < :last_post_id
ORDER BY id DESC
LIMIT 15;
這樣不僅避免了 OFFSET,還能保持時間排序的正確性。
3. 後台管理系統的資料審核
管理員需要檢視待審核的申請,每頁 30 筆。為了讓分頁切換更快,先在 applications 表的 status 欄位上建立索引,並使用:
SELECT id, applicant_name, submitted_at
FROM applications
WHERE status = 'pending'
ORDER BY submitted_at DESC
LIMIT 30 OFFSET :offset;
若待審核筆數超過 500,000 筆,則改採 分段查詢(根據 submitted_at 時間區間)來降低掃描量。
總結
LIMIT 是 MySQL 中實作分頁的核心工具,配合 ORDER BY、適當索引 以及 鍵值分頁 的技巧,能讓資料查詢既 快速 又 穩定。在設計分頁功能時,務必要:
- 明確指定排序,避免結果不一致。
- 盡量避免大
OFFSET,改用鍵值分頁或分段查詢。 - 只取必要欄位,利用覆蓋索引提升效能。
- 針對總筆數的需求,選擇合適的計算方式(
SQL_CALC_FOUND_ROWS、獨立COUNT(*)或快取)。
掌握以上概念與實務技巧,你就能在任何 MySQL 專案中,輕鬆實作出高效、可擴充的分頁查詢,提升使用者體驗與系統效能。祝開發順利!