分頁是後台管理、列表頁的核心功能。本文從基礎分頁邏輯到大數據場景優化、現代框架集成,提供生產級分頁實現方案。
$pageSize:每頁條數(如 10、20、50)$pageNow:當前頁碼(從 1 開始)$rowCount:總記錄數(需 SELECT COUNT(*) 獲取)$pageCount:總頁數 = ceil($rowCount / $pageSize)<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
$pageSize = 10;
$pageNow = max(1, (int)($_GET['page'] ?? 1));
// 1. 獲取總記錄數
$stmt = $pdo->query("SELECT COUNT(*) FROM users");
$rowCount = $stmt->fetchColumn();
$pageCount = (int)ceil($rowCount / $pageSize);
// 2. 修正頁碼邊界
$pageNow = min($pageNow, max(1, $pageCount));
// 3. 計算偏移量並查詢數據
$offset = ($pageNow - 1) * $pageSize;
$stmt = $pdo->prepare("SELECT * FROM users ORDER BY id DESC LIMIT :offset, :size");
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->bindValue(':size', $pageSize, PDO::PARAM_INT);
$stmt->execute();
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!-- 顯示數據 -->
<table>
<thead><tr><th>ID</th><th>用戶名</th><th>郵箱</th></tr></thead>
<tbody>
<?php foreach ($users as $u): ?>
<tr><td><?= $u['id'] ?></td><td><?= htmlspecialchars($u['username']) ?></td><td><?= $u['email'] ?></td></tr>
<?php endforeach; ?>
</tbody>
</table>
<!-- 分頁導航 -->
<div class="pagination">
<?php if ($pageNow > 1): ?>
<a href="?page=1">首頁</a>
<a href="?page=<?= $pageNow - 1 ?>">上一頁</a>
<?php endif; ?>
<?php
// 顯示頁碼範圍:當前頁前後各 2 頁
$start = max(1, $pageNow - 2);
$end = min($pageCount, $pageNow + 2);
for ($i = $start; $i <= $end; $i++): ?>
<a href="?page=<?= $i ?>" class="<?= $i == $pageNow ? 'active' : '' ?>"><?= $i ?></a>
<?php endfor; ?>
<?php if ($pageNow < $pageCount): ?>
<a href="?page=<?= $pageNow + 1 ?>">下一頁</a>
<a href="?page=<?= $pageCount ?>">尾頁</a>
<?php endif; ?>
<span class="info">第 <?= $pageNow ?> / <?= $pageCount ?> 頁,共 <?= $rowCount ?> 條</span>
</div>
問題:LIMIT offset, size 在大偏移量時性能極差(MySQL 需掃描前 offset 行)。
<?php
// 利用唯一遞增字段(如 id、created_at+id)作為游標
$pageSize = 20;
$cursor = (int)($_GET['cursor'] ?? 0); // 上一頁最後一條的 id
$direction = $_GET['dir'] ?? 'next'; // 'next' 或 'prev'
if ($direction === 'next') {
$stmt = $pdo->prepare("SELECT * FROM users WHERE id > :cursor ORDER BY id ASC LIMIT :size");
$stmt->bindValue(':cursor', $cursor, PDO::PARAM_INT);
} else {
$stmt = $pdo->prepare("SELECT * FROM users WHERE id < :cursor ORDER BY id DESC LIMIT :size");
$stmt->bindValue(':cursor', $cursor, PDO::PARAM_INT);
}
$stmt->bindValue(':size', $pageSize, PDO::PARAM_INT);
$stmt->execute();
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 前端只需傳遞 last_id 作為下一頁 cursor
// 缺點:無法跳轉到任意頁,僅支持「上一頁/下一頁」或無限滾動
?>
-- 先只查主鍵 ID,再回表查詳情 SELECT u.* FROM users u JOIN (SELECT id FROM users ORDER BY id DESC LIMIT 100000, 20) tmp USING (id) ORDER BY u.id DESC;
$rowCount = $redis->get('users:total_count');
if ($rowCount === false) {
$rowCount = $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn();
$redis->setex('users:total_count', 300, $rowCount); // 緩存 5 分鐘
}
// Laravel
$users = User::orderBy('id', 'desc')->paginate(15);
return view('users.index', compact('users'));
// Blade 模板
{{ $users->links() }} // 自動生成完整分頁導航
// ThinkPHP 6+
$list = Db::name('users')->order('id desc')->paginate(15);
$this->assign('list', $list);
// 模板:{$list->render()}
// Yii2
use yii\data\Pagination;
$query = User::find()->orderBy(['id' => SORT_DESC]);
$pages = new Pagination(['totalCount' => $query->count(), 'pageSize' => 15]);
$models = $query->offset($pages->offset)->limit($pages->limit)->all();
// 視圖:echo LinkPager::widget(['pagination' => $pages]);
<nav aria-label="分頁導航"><ul class="pagination">...aria-current="page",禁用狀態 aria-disabled="true"選型指南:數據量 < 10 萬 → 直接
LIMIT offset, size;10 萬 - 100 萬 → 延遲關聯或緩存總數;> 100 萬 → **強制使用游標分頁**,放棄任意頁跳轉,改用無限滾動/載入更多交互模式。