分頁核心在於三個數字的關係:總筆數($total)、每頁筆數($length)、目前頁碼($page)。本文提供一個最精簡、註解完整的教學範例,展示如何計算 LIMIT offset, length、處理首頁/尾頁邊界、產生上一頁/下一頁連結。
<?php
header("Content-Type: text/html; charset=utf-8");
// 1. 資料庫連線(教學用 mysqli,實務建議 PDO)
$mysqli = new mysqli("localhost", "root", "111", "test");
if ($mysqli->connect_errno) die("連線失敗: " . $mysqli->connect_error);
$mysqli->set_charset("utf8");
// 2. 取得總筆數
$total = (int)$mysqli->query("SELECT COUNT(*) FROM user")->fetch_row()[0];
// 3. 分頁參數
$length = 5; // 每頁 5 筆
$totalPage = (int)ceil($total / $length); // 總頁數
$page = isset($_GET['p']) ? max(1, (int)$_GET['p']) : 1; // 目前頁碼,最小 1
$page = min($page, $totalPage); // 修正超出總頁數
$offset = ($page - 1) * $length; // LIMIT 起始位移
// 4. 查詢當頁資料
$sql = "SELECT * FROM user ORDER BY id LIMIT ?, ?";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param("ii", $offset, $length);
$stmt->execute();
$result = $stmt->get_result();
// 5. 輸出 HTML 表格
echo "PHP 分頁教學
";
echo "ID USER PASS ";
while ($row = $result->fetch_assoc()) {
echo "{$row['id']} {$row['name']} {$row['pass']} ";
}
echo "
";
// 6. 上一頁 / 下一頁 連結
$prevPage = max(1, $page - 1);
$nextPage = min($totalPage, $page + 1);
echo "";
if ($page > 1) {
echo "上一頁 | ";
}
if ($page < $totalPage) {
echo "下一頁";
}
echo " (第 {$page} / {$totalPage} 頁)
";
echo " ";
總筆數 $total = 17 每頁筆數 $length = 5 總頁數 $totalPage = ceil(17/5) = 4 頁碼 $page = 1 → offset = 0 → LIMIT 0,5 → 第 1-5 筆 頁碼 $page = 2 → offset = 5 → LIMIT 5,5 → 第 6-10 筆 頁碼 $page = 3 → offset = 10 → LIMIT 10,5 → 第 11-15 筆 頁碼 $page = 4 → offset = 15 → LIMIT 15,5 → 第 16-17 筆(最後一頁不足 5 筆)
| 情境 | 程式碼 | 說明 |
|---|---|---|
| 使用者輸入頁碼 < 1 | max(1, (int)$_GET['p']) | 強制轉為 1 |
| 使用者輸入頁碼 > 總頁數 | min($page, $totalPage) | 自動導向最後一頁 |
| 第一頁不顯示「上一頁」 | if ($page > 1) | 避免產生無效連結 |
| 最後一頁不顯示「下一頁」 | if ($page < $totalPage) | 同理 |
| 總筆數為 0 | $totalPage = 0,迴圈不執行 | 需額外判斷顯示「無資料」 |
$window = 2; // 當前頁前後各顯示 2 頁
$start = max(1, $page - $window);
$end = min($totalPage, $page + $window);
for ($i = $start; $i <= $end; $i++) {
if ($i == $page) {
echo "{$i} ";
} else {
echo "{$i} ";
}
}
// 若 $start > 1 顯示「首頁 ...」
// 若 $end < $totalPage 顯示「... 尾頁」
SELECT COUNT(*) 較慢,可快取總筆數或使用 SQL_CALC_FOUND_ROWS + FOUND_ROWS()(MySQL 8.0+ 棄用,建議單獨查詢並快取)。ORDER BY id 應有索引,避免 filesort。WHERE id > :lastId ORDER BY id LIMIT 5,避免 OFFSET 深分頁效能崩塌。分頁本質是「offset = (page-1)*length」的數學關係,加上邊界修正與連結產生。掌握此核心,即可在任何框架(Laravel paginate()、ThinkPHP paginate()、原生 PDO)中理解其底層運作。教學階段建議先寫出此原生版本,再遷移至框架內建分頁器,避免「只會呼叫 API、不懂原理」的盲區。