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

留言板分頁機制完整解析:邏輯、程式碼與優化

     編輯:PHP 綜合知識

核心需求

  • 依時間倒序顯示留言
  • 每頁固定筆數
  • 自動計算總頁數
  • 產生「首頁/上一頁/頁碼/下一頁/尾頁」導航
  • 邊界處理:首頁無上一頁、尾頁無下一頁

資料庫準備

CREATE TABLE `note` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `content` TEXT NOT NULL,
  `sendtime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `author` VARCHAR(50) DEFAULT NULL,
  INDEX `idx_sendtime` (`sendtime`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

分頁核心邏輯

<?php
// config.php
$host = 'localhost'; $user = 'root'; $pass = ''; $db = 'guestbook';
$perPage = 10; // 每頁筆數

// list.php
require 'config.php';
$conn = new mysqli($host, $user, $pass, $db);
$conn->set_charset('utf8mb4');

// 1. 取得並驗證頁碼
$page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;

// 2. 查詢總筆數
$totalResult = $conn->query("SELECT COUNT(*) FROM `note`");
$totalRows = $totalResult->fetch_row()[0];
$totalResult->free();

// 3. 計算總頁數
$totalPages = (int)ceil($totalRows / $perPage);
if ($page > $totalPages) $page = max(1, $totalPages);

// 4. 計算 OFFSET
$offset = ($page - 1) * $perPage;

// 5. 分頁查詢(使用 LIMIT offset, count)
$stmt = $conn->prepare("SELECT id, content, author, sendtime FROM `note` ORDER BY sendtime DESC LIMIT ?, ?");
$stmt->bind_param("ii", $offset, $perPage);
$stmt->execute();
$result = $stmt->get_result();

// 6. 顯示留言
echo "<table width='100%'>";
while ($row = $result->fetch_assoc()) {
    $time = date('Y-m-d H:i', strtotime($row['sendtime']));
    $content = nl2br(htmlspecialchars($row['content']));
    echo "<tr><td><strong>{$row['author']}</strong> $time<br>$content</td></tr>";
}
echo "</table>";
$result->free();
$stmt->close();

// 7. 產生分頁導航
echo "<div class='pagination' style='text-align:center; margin:20px 0;'>";

if ($page > 1) {
    echo "<a href='?page=1'>首頁</a> ";
    echo "<a href='?page=" . ($page - 1) . "'>上一頁</a> ";
}

// 顯示頁碼範圍(當前頁前後各 2 頁)
$start = max(1, $page - 2);
$end = min($totalPages, $page + 2);
for ($i = $start; $i <= $end; $i++) {
    if ($i == $page) {
        echo "<span class='current' style='font-weight:bold; color:red;'>[$i]</span> ";
    } else {
        echo "<a href='?page=$i'>$i</a> ";
    }
}

if ($page < $totalPages) {
    echo "<a href='?page=" . ($page + 1) . "'>下一頁</a> ";
    echo "<a href='?page=$totalPages'>尾頁</a> ";
}

echo "<span class='info'>(第 $page / $totalPages 頁,共 $totalRows 筆)</span>";
echo "</div>";

$conn->close();
?>

關鍵變數對照表

變數意義計算方式
$perPage每頁筆數固定常數或設定檔
$page當前頁碼 (1-based)max(1, intval($_GET['page']))
$totalRows總資料筆數SELECT COUNT(*)
$totalPages總頁數ceil($totalRows / $perPage)
$offset跳過筆數($page - 1) * $perPage
LIMIT ?, ?MySQL 分頁語法LIMIT offset, count

原始碼邏輯與現代寫法對比

面向原始碼現代改良
資料庫介面mysql_* (已移除)mysqli / PDO + Prepared Statement
查詢方式先查全部再 PHP 陣列切片SQL LIMIT 直接取所需頁面
總筆數查詢mysql_numrows($result) (錯誤用法)獨立 COUNT(*) 查詢
陣列索引$show[$i][content] (語法錯誤)直接迴圈輸出 fetch_assoc()
URL 參數手動拼接 $qqname=$qqname&serial=$serialhttp_build_query() 自動編碼
頁碼顯示全部列出當前頁附近範圍 (如 ±2)
XSS 防護無htmlspecialchars() 輸出轉義

效能優化策略

1. 避免深分頁效能問題

大偏移量 LIMIT 100000, 10 會掃描前 10 萬行。解決方案:

  • 游標分頁:記住上一頁最後一筆的 PK/時間,用 WHERE id > ? ORDER BY id LIMIT 10(需唯一排序鍵)。
  • 延遲關聯:SELECT * FROM note WHERE id IN (SELECT id FROM note ORDER BY sendtime DESC LIMIT 100000, 10) 先只查 ID 再回表。
  • 限制最大頁碼:如僅允許前 100 頁,超過導向搜尋/篩選。

2. 總筆數快取

// Redis 快取 1 分鐘
$cacheKey = 'note:total_count';
$totalRows = $redis->get($cacheKey);
if ($totalRows === false) {
    $totalRows = $conn->query("SELECT COUNT(*) FROM note")->fetch_row()[0];
    $redis->setex($cacheKey, 60, $totalRows);
}

3. 覆蓋索引

-- 僅查詢需要的欄位,建立覆蓋索引避免回表
CREATE INDEX idx_note_paging ON note (sendtime DESC, id) INCLUDE (content, author); -- MySQL 8.0+
-- 或組合索引
CREATE INDEX idx_note_paging ON note (sendtime DESC, id, content, author);

現代框架整合範例

Laravel Eloquent

// Controller
$notes = Note::latest('sendtime')->paginate(10);

// Blade 模板
@foreach ($notes as $note)
    <div>{{ $note->author }} - {{ $note->sendtime->format('Y-m-d H:i') }}<br>{{ $note->content }}</div>
@endforeach
{{ $notes->links() }} // 自動產生分頁導航

PDO 通用分頁類別

class Paginator {
    public function __construct(
        private PDO $pdo,
        private string $table,
        private int $perPage = 10,
        private string $orderBy = 'id DESC',
        private string $where = '',
        private array $params = []
    ) {}

    public function getPage(int $page = 1): array {
        $page = max(1, $page);
        $total = $this->pdo->query("SELECT COUNT(*) FROM {$this->table} {$this->where}", $this->params)->fetchColumn();
        $totalPages = (int)ceil($total / $this->perPage);
        $page = min($page, max(1, $totalPages));
        $offset = ($page - 1) * $this->perPage;

        $stmt = $this->pdo->prepare("SELECT * FROM {$this->table} {$this->where} ORDER BY {$this->orderBy} LIMIT ?, ?");
        $stmt->execute(array_merge($this->params, [$offset, $this->perPage]));
        $items = $stmt->fetchAll();

        return [
            'items' => $items,
            'current_page' => $page,
            'per_page' => $this->perPage,
            'total' => $total,
            'total_pages' => $totalPages,
            'has_prev' => $page > 1,
            'has_next' => $page < $totalPages,
        ];
    }
}

// 使用
$pager = new Paginator($pdo, 'note', 10, 'sendtime DESC');
$data = $pager->getPage($_GET['page'] ?? 1);

常見錯誤與除錯

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
錯誤現象可能原因解決
第 1 頁正常,第 2 頁空白OFFSET 計算錯誤或 LIMIT 參數順序反確認 LIMIT offset, count 順序
總頁數為 0COUNT(*) 查詢失敗或資料表為空檢查 SQL 錯誤、資料是否存在
頁碼連結點擊無反應URL 參數名稱不一致(page vs p)統一參數名,使用 $_GET['page']
中文亂碼連線 charset 未設定$conn->set_charset('utf8mb4')
分頁導航顯示錯誤邊界條件判斷錯誤(<= vs <)仔細檢查 $page > 1, $page < $totalPages