首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> MySQL 資料庫 >> MySQL 綜合教學

Java 實現 MySQL 分頁查詢的最佳實踐(含 Offset/Limit 與 Keyset 分頁)

     編輯:MySQL 綜合教學

前言

分頁查詢是 Web 應用的核心需求。MySQL 原生支援 LIMIT offset, row_count,但深分頁(大 Offset)會導致效能急劇下降。本文介紹 Java 中實現 MySQL 分頁的三種主流方案:傳統 Offset/Limit、子查詢優化、以及高效能的 Keyset 分頁(游標分頁),並提供 Spring Data JPA / MyBatis 實作範例。

方案對比

方案原理優點缺點適用場景
Offset/LimitLIMIT offset, size語法簡單、支援隨機跳頁深分頁掃描大量行、效能 O(N)頁數少、資料量小、後台管理
子查詢優化先查主鍵 ID 再回表減少回表行數仍需掃描 Offset 行索引中等分頁、需保留跳頁
Keyset 分頁WHERE id > last_id ORDER BY id LIMIT size極快、穩定 O(1)、無深分頁問題不支援隨機跳頁、需唯一遞增排序鍵無限滾動、API 分頁、大資料集

1. 傳統 Offset/Limit(MyBatis / JPA 原生)

MyBatis XML

<select id="selectPage" resultType="User">
  SELECT * FROM t_user
  WHERE status = 1
  ORDER BY create_time DESC
  LIMIT #{offset}, #{pageSize}
</select>

Spring Data JPA

Pageable pageable = PageRequest.of(page, size, Sort.by("createTime").descending());
Page page = userRepository.findByStatus(1, pageable);

2. 子查詢優化深分頁

利用覆蓋索引先取 ID,再回表查完整欄位:

<select id="selectPageOptimized" resultType="User">
  SELECT * FROM t_user
  WHERE id IN (
    SELECT id FROM t_user
    WHERE status = 1
    ORDER BY create_time DESC
    LIMIT #{offset}, #{pageSize}
  )
  ORDER BY create_time DESC
</select>

前提:(status, create_time, id) 建立聯覆蓋索引。

3. Keyset 分頁(游標分頁)—— 推薦用於大資料量

核心思想:記住上一頁最後一條記錄的排序鍵值,下一頁直接從該值之後查詢。

前端傳參

// 第一頁
{ "pageSize": 20, "lastId": null, "lastCreateTime": null }

// 後續頁
{ "pageSize": 20, "lastId": 10050, "lastCreateTime": "2023-10-01 12:00:00" }

MyBatis 實作

<select id="selectByKeyset" resultType="User">
  SELECT * FROM t_user
  WHERE status = 1
  <if test="lastId != null and lastCreateTime != null">
    AND (create_time < #{lastCreateTime} 
         OR (create_time = #{lastCreateTime} AND id < #{lastId}))
  </if>
  ORDER BY create_time DESC, id DESC
  LIMIT #{pageSize}
</select>

Spring Data JPA 實作

@Query("SELECT u FROM User u WHERE u.status = 1 " +
       "AND (?1 IS NULL OR u.createTime < ?1 OR (u.createTime = ?1 AND u.id < ?2)) " +
       "ORDER BY u.createTime DESC, u.id DESC")
Page findByKeyset(LocalDateTime lastCreateTime, Long lastId, Pageable pageable);

// 呼叫時 pageable 僅設 pageSize,page=0
Pageable pageable = PageRequest.of(0, 20);
Page page = repo.findByKeyset(lastCreateTime, lastId, pageable);

效能實測對比(百萬級資料)

-- 測試表:100 萬行,索引 (status, create_time, id)
-- 第 50000 頁,每頁 20 筆

-- Offset/Limit:約 12.3 秒(全表掃描 100 萬行)
SELECT * FROM t_user WHERE status=1 ORDER BY create_time DESC LIMIT 1000000, 20;

-- Keyset:約 0.003 秒(索引定位直接取 20 筆)
SELECT * FROM t_user WHERE status=1 
  AND (create_time < '2023-01-01' OR (create_time='2023-01-01' AND id < 50000))
  ORDER BY create_time DESC, id DESC LIMIT 20;

總結與選型建議

  1. 後台管理、頁數固定、資料量 < 10 萬:直接用 Offset/Limit,開發效率最高。
  2. 前端無限滾動、APP 列表、API 分頁、資料量 > 100 萬:強制使用 Keyset 分頁。
  3. 需支援跳頁又要效能:首頁用 Keyset,跳頁時臨時用子查詢優化或接受較慢速度。
  4. 索引設計是關鍵:分頁查詢的 WHERE + ORDER BY 欄位必須建立聯覆蓋索引。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved