傳統分頁需要兩次資料庫查詢:COUNT(*) 取總筆數,再 LIMIT 取當頁資料。面對大數據量與高併發,這造成嚴重效能瓶頸。本文提出「首次查詢總數,後續透過 URL 參數傳遞」的優化策略,將分頁查詢從 2N 降為 N+1。
// 每次分頁皆執行
$sqlCount = "SELECT COUNT(*) AS total FROM tablename";
$total = mysql_fetch_assoc(mysql_query($sqlCount))['total'];
$sqlData = "SELECT * FROM tablename LIMIT " . ($page-1)*$pageSize . ", $pageSize";
$data = mysql_query($sqlData);
缺點:翻頁、排序、篩選每次皆觸發 COUNT(*),鎖表、掃描全表,MyISAM 尤其明顯。
// 首次載入或無 total 參數時查詢總數
if (isset($_GET['total'])) {
$total = intval($_GET['total']);
} else {
$sqlCount = "SELECT COUNT(*) AS total FROM tablename";
$total = mysql_fetch_assoc(mysql_query($sqlCount))['total'];
}
$page = max(1, intval($_GET['page'] ?? 1));
$sqlData = "SELECT * FROM tablename LIMIT " . ($page-1)*$pageSize . ", $pageSize";
$data = mysql_query($sqlData);
// 分頁連結攜帶 total 參數
$url = "url.php?total=$total&page=";
echo $pagination->render($url, $total, $pageSize, $page);
| 方案 | 原理 | 優點 | 缺點 |
|---|---|---|---|
| URL 傳遞 total | 客戶端保持狀態 | 零伺服器快取、實作簡單 | URL 較長、用戶可竄改總數 |
| Redis/Memcached 快取總數 | 伺服器端快取 5-60 秒 | 精準度高、不暴露參數 | 需維護快取失效策略 |
| SQL_CALC_FOUND_ROWS | MySQL 內建一次查詢同時獲取總數 | 單次查詢、語法標準 | MySQL 8.0 棄用、鎖表風險 |
| 游標分頁 | 以唯一遞增 ID 為游標 | 極高效能、無 OFFSET 深分頁問題 | 不支援跳轉任意頁、需 ID 連續 |
| 預估總數 | EXPLAIN rows 或資訊統計表 | 零成本 | 不精確、僅適用大數據近似 |
intval() 強制轉型,防止 SQL 注入;總數參數加簽名驗證。paginate()、Symfony Paginator 內建最佳化,優先使用。COUNT(*)(N+1 問題)。OFFSET 1000000 LIMIT 10 深分頁,改用 WHERE id > last_id LIMIT 10。COUNT(*) 無 WHERE 條件較快,有條件需走索引,考慮觸發器維護計數表。