---
title: "DB-PHP 讀取 MySQL 全部紀錄（分頁表格顯示） (tc)"
description: 本文說明如何用 PHP 讀取 MySQL 資料表全部記錄，並以「分頁 + 表格」形式顯示，避免一次載入過多資料拖慢頁面。
url: "https://aaronchan.com/zh/kb/php-read-mysql-paginated-table/"
updated: "2026-09-18"
category: "Code-php+MySQL (tc)"
---

# DB-PHP 讀取 MySQL 全部紀錄（分頁表格顯示） (tc)

## 🧩 概念與核心語法

當資料表有成千上萬筆記錄時，一次載入全部會拖慢速度、影響閱讀。分頁（Pagination）的做法是用 MySQL 的 `LIMIT` 子句，每次只取指定數量的記錄。

### LIMIT 語法（兩種寫法，意思一樣）

 Copy

```
-- Syntax 1: offset comes first
SELECT * FROM table_name LIMIT offset, row_count;

-- Syntax 2: using the OFFSET keyword (more readable)
SELECT * FROM table_name LIMIT row_count OFFSET offset;
```

| 參數 | 說明 |
| --- | --- |
| `row_count` | 每頁顯示多少筆記錄 |
| `offset` | 要跳過多少筆才開始取（由 0 開始，`OFFSET 0` 即第一筆） |

### 分頁核心數學（三個變數）

 Copy

```
$per_page = 10;                                   // rows per page
$page     = isset($_GET['page']) ? (int)$_GET['page'] : 1;  // current page
$offset   = ($page - 1) * $per_page;              // rows to skip
```

✅ 第 1 頁 → `offset = 0`；第 2 頁 → `offset = 10`；第 3 頁 → `offset = 20`

---

## 📊 完整範例（mysqli 程序式）

以下是一個可直接執行的完整範例，用 `mysqli` 讀取 `students` 資料表並以分頁表格顯示：

 Copy

```
<?php
// 1. Connect to the database
$conn = mysqli_connect('localhost', 'root', '', 'pagination');
if (!$conn) {
    die('Connection failed: ' . mysqli_connect_error());
}
mysqli_set_charset($conn, 'utf8mb4');   // support Chinese, avoid mojibake

// 2. Pagination variables
$per_page = 5;                                        // rows per page
$page     = max(1, (int)($_GET['page'] ?? 1));        // current page (min 1)
$offset   = ($page - 1) * $per_page;                  // rows to skip

// 3. Get total records, then compute total pages
$total_result = mysqli_query($conn, 'SELECT COUNT(*) FROM students');
$total_records = mysqli_fetch_row($total_result)[0];
$total_pages = (int) ceil($total_records / $per_page);  // ceil rounds up

// 4. Fetch current-page records using LIMIT + OFFSET
$sql = "SELECT * FROM students ORDER BY id LIMIT $offset, $per_page";
$result = mysqli_query($conn, $sql);

// 5. Render the table
echo '<table border="1">';
echo '<tr><th>ID</th><th>Name</th><th>Age</th></tr>';
while ($row = mysqli_fetch_assoc($result)) {
    echo '<tr>';
    echo '<td>' . htmlspecialchars($row['id']) . '</td>';
    echo '<td>' . htmlspecialchars($row['name']) . '</td>';
    echo '<td>' . htmlspecialchars($row['age']) . '</td>';
    echo '</tr>';
}
echo '</table>';

// 6. Pagination navigation links
for ($i = 1; $i <= $total_pages; $i++) {
    if ($i == $page) {
        echo "<strong>$i</strong> ";          // bold the current page
    } else {
        echo "<a href='?page=$i'>$i</a> ";
    }
}

mysqli_close($conn);
?>
```

---

## 🔒 安全性（Prepared Statement）

直接把 `$offset`、`$per_page` 拼入 SQL 有 SQL injection（SQL 注入）風險。用 prepared statement 綁定整數參數才是正確做法：

 Copy

```
<?php
$conn = mysqli_connect('localhost', 'root', '', 'pagination');

$per_page = 5;
$page     = max(1, (int)($_GET['page'] ?? 1));
$offset   = ($page - 1) * $per_page;

// use ? placeholders, then bind integer parameters
$stmt = mysqli_prepare($conn, 'SELECT id, name, email FROM users ORDER BY id LIMIT ? OFFSET ?');
mysqli_stmt_bind_param($stmt, 'ii', $per_page, $offset);   // 'ii' = two integers
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);

while ($row = mysqli_fetch_assoc($result)) {
    echo htmlspecialchars($row['name']) . '<br>';
}
mysqli_close($conn);
?>
```

### PDO 版本（更推薦）

 Copy

```
<?php
$pdo = new PDO('mysql:host=localhost;dbname=demo', 'root', '');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$per_page = 20;
$page     = max(1, (int)($_GET['page'] ?? 1));
$offset   = ($page - 1) * $per_page;

// total record count
$total_records = (int) $pdo->query('SELECT COUNT(*) FROM users')->fetchColumn();
$total_pages   = (int) ceil($total_records / $per_page);

// fetch the current page
$stmt = $pdo->prepare('SELECT id, name, email FROM users ORDER BY id LIMIT :limit OFFSET :offset');
$stmt->bindValue(':limit', $per_page, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

foreach ($rows as $row) {
    echo htmlspecialchars($row['name']) . '<br>';
}
?>
```

---

## ⚠️ 注意事項

| 注意事項 | 說明 |
| --- | --- |
| 必配 `ORDER BY` | 沒有 `ORDER BY` 時 MySQL 可任意排序，分頁結果會不穩定。一定要配 `ORDER BY` 唯一欄位（如 `id`） |
| 兩次查詢 | 分頁需要兩條 query：一條 `COUNT(*)` 取總筆數、一條 `LIMIT/OFFSET` 取目前頁資料 |
| offset 由 0 開始 | 常見 off-by-one 錯誤：第 1 頁要 `OFFSET 0`，所以公式是 `($page - 1) * $per_page` |
| 深分頁慢 | `LIMIT 20 OFFSET 100000` 會令 MySQL 掃過前面 10 萬筆。大型資料建議用 keyset pagination（`WHERE id > :lastId`） |
| 中文亂碼 | 記得 `mysqli_set_charset($conn, 'utf8mb4')`，否則中文會變亂碼 |
| 輸出要 escape | 顯示前用 `htmlspecialchars()` 防止 XSS（跨站腳本攻擊） |

---

## 🛠️ 快速對照

| 項目 | mysqli（程序式） | PDO |
| --- | --- | --- |
| 連線 | `mysqli_connect()` | `new PDO('mysql:host=...;dbname=...')` |
| 取結果 | `mysqli_fetch_assoc()` | `fetchAll(PDO::FETCH_ASSOC)` |
| 綁定參數 | `mysqli_stmt_bind_param()` | `bindValue()` |
| 支援多資料庫 | 只支援 MySQL | 多種資料庫 |
| 推薦程度 | 一般 | ✅ 更推薦（更安全、易維護） |

---

## 🔗 參考連結

| 來源 | 連結 |
| --- | --- |
| W3docs — MySQL LIMIT | [w3docs.com](https://www.w3docs.com/learn-php/mysql-limit-data) |
| Tutorial Republic — LIMIT 教學 | [tutorialrepublic.com](https://www.tutorialrepublic.com/php-tutorial/php-mysql-limit-clause.php) |
| CodeShack — PHP Pagination | [codeshack.io](https://codeshack.io/how-to-create-pagination-php-mysql) |
| Mike Lopez — 精簡分頁 | [mikelopez.com](https://mikelopez.com/blog/1118/) |
| Campcodes — 自訂分頁 | [campcodes.com](https://www.campcodes.com/blog/php-pagination-tutorial-build-custom-pagination-with-mysql-2026/) |
