萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 網路程式設計 >> PHP 程式設計

PHP 分頁技術完整指南:原理、SQL LIMIT 優化、分頁類與前端組件

分頁是 Web 應用處理大量數據的標準模式。核心邏輯只需三個參數:每頁條數(PageSize)、當前頁碼(CurrentPage)、總記錄數。本文從 SQL LIMIT 原理到完整分頁類實現、前端渲染、效能優化全鏈路講解。

核心原理:SQL LIMIT 偏移量計算

-- MySQL LIMIT 語法:LIMIT offset, rows
-- offset = (當前頁 - 1) * 每頁條數

-- 第 1 頁(10 條/頁)
SELECT * FROM articles LIMIT 0, 10;

-- 第 2 頁
SELECT * FROM articles LIMIT 10, 10;

-- 第 N 頁通用模板
SELECT * FROM table ORDER BY id DESC LIMIT (page-1)*pageSize, pageSize;

基礎分頁實現(程式碼版)

<?php
// 1. 獲取參數
$page = max(1, (int)($_GET['page'] ?? 1));
$pageSize = 10;

// 2. 獲取總記錄數(必要,用於計算總頁數)
$countSql = "SELECT COUNT(*) AS total FROM articles";
$total = (int)$pdo->query($countSql)->fetchColumn();

// 3. 計算總頁數
$pageCount = (int)ceil($total / $pageSize);
$page = min($page, max(1, $pageCount)); // 修正越界頁碼

// 4. 查詢當前頁數據
$offset = ($page - 1) * $pageSize;
$sql = "SELECT * FROM articles ORDER BY created_at DESC LIMIT ?, ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$offset, $pageSize]);
$rows = $stmt->fetchAll();

// 5. 構建分頁鏈接
$links = '';
if ($page > 1) {
    $links .= '首頁 ';
    $links .= '上一頁 ';
}
for ($i = max(1, $page-2); $i <= min($pageCount, $page+2); $i++) {
    $links .= $i == $page ? "$i " : "$i ";
}
if ($page < $pageCount) {
    $links .= "下一頁 ";
    $links .= "尾頁";
}

// 6. 輸出
foreach ($rows as $row) { /* 顯示數據 */ }
echo "";
?>

企業級分頁類實現(支援 JS 回調、自定義樣式)

<?php
class Paginator {
    public int $pageNo = 1;
    public int $pageSize = 20;
    public int $totalCount = 0;
    public int $pageCount = 0;
    public int $offset = 0;
    public array $items = [];
    public bool $hasPrev = false;
    public bool $hasNext = false;
    public string $jsCallback = 'loadPage'; // 前端 JS 回調函數名
    
    public function __construct(
        int $totalCount,
        int $pageSize = 20,
        int $pageNo = 1,
        array $items = [],
        string $jsCallback = 'loadPage'
    ) {
        $this->totalCount = $totalCount;
        $this->pageSize = max(1, $pageSize);
        $this->pageNo = max(1, $pageNo);
        $this->items = $items;
        $this->jsCallback = $jsCallback;
        
        $this->calculate();
    }
    
    private function calculate(): void {
        $this->pageCount = max(1, (int)ceil($this->totalCount / $this->pageSize));
        $this->pageNo = min($this->pageNo, $this->pageCount);
        $this->offset = ($this->pageNo - 1) * $this->pageSize;
        $this->hasPrev = $this->pageNo > 1;
        $this->hasNext = $this->pageNo < $this->pageCount;
    }
    
    /** 生成頁碼陣列:[1, 2, -1, 10] -1 代表省略號 */
    public function getPageNumbers(): array {
        $list = [];
        $pc = $this->pageCount;
        $pn = $this->pageNo;
        
        if ($pc <= 9) {
            $list = range(1, $pc);
        } elseif ($pn <= 4) {
            $list = array_merge(range(1, 5), [-1, $pc]);
        } elseif ($pn > $pc - 4) {
            $list = array_merge([1, -1], range($pc - 4, $pc));
        } else {
            $list = array_merge([1, -1], range($pn - 2, $pn + 2), [-1, $pc]);
        }
        return $list;
    }
    
    /** 渲染 HTML 分頁條(Bootstrap 5 風格) */
    public function render(): string {
        $html = "";
        return $html;
    }
}

// 使用範例
$pageNo = (int)($_GET['page'] ?? 1);
$pageSize = 15;
$items = Article::getPage($pageNo, $pageSize); // 返回當前頁數據陣列
$total = Article::getTotalCount();            // 返回總數

$paginator = new Paginator($total, $pageSize, $pageNo, $items);
echo $paginator->render();
?>

前端 JS 交互(AJAX 無刷新分頁)

<script>
function loadPage(pageNo) {
    fetch(`api/articles?page=${pageNo}&size=15`)
        .then(r => r.json())
        .then(data => {
            renderArticles(data.items);      // 渲染列表
            document.getElementById('pagination').innerHTML = data.paginationHtml; // 更新分頁條
            window.history.pushState({}, '', `?page=${pageNo}`); // 更新 URL
        });
}

function renderArticles(items) {
    const html = items.map(item => `
        

${escapeHtml(item.title)}

${escapeHtml(item.excerpt)}

`).join(''); document.getElementById('article-list').innerHTML = html; } function escapeHtml(str) { return str.replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); } </script>

效能優化關鍵點

1. 深分頁問題

當 OFFSET 很大時(如第 10000 頁),MySQL 需掃描前 100000 行再丟棄,極慢。

解決方案:

  • 游標分頁:WHERE id > last_seen_id ORDER BY id LIMIT 20(需 ID 遞增且不需跳頁)
  • 延遲關聯:SELECT * FROM t INNER JOIN (SELECT id FROM t ORDER BY id LIMIT 100000, 20) tmp USING (id)
  • 覆蓋索引:只查主鍵 ID 子查詢,再回表

2. 總數查詢優化

  • 大表 COUNT(*) 慢 → 緩存總數(Redis)、近似值(EXPLAIN rows)、異步更新計數表
  • 不需精確總頁數時可用「加載更多」無限滾動替代傳統分頁

3. 索引覆蓋

分頁查詢的 ORDER BY 字段必須有索引,避免 filesort。

總結

分頁三要素:PageSize、CurrentPage、TotalCount。核心 SQL:LIMIT (page-1)*size, size。工程化要點:

  1. 封裝 Paginator 類,分離業務邏輯與顯示
  2. 前端 AJAX + History API 實現無刷新體驗
  3. 深分頁用游標/延遲關聯優化
  4. 總數緩存、索引覆蓋、避免 SELECT *

掌握這套體系,從幾十條數據到億級表,都能構建流暢、高效的分頁體驗。

copyright © 萬盛學電腦網 all rights reserved