PHP分页核心是确保当前页与总条数对齐:先查COUNT()得总数,再算$offset=($page-1)$per_page,$page须强转校验且≥1,$per_page应白名单限制,总页数用ceil($total_count/$per_page)计算。

PHP 分页不是写个循环加个 limit 就完事——核心在于把「当前页」和「总条数」对齐,否则翻到第二页就漏数据或重复。
怎么算出总页数和当前偏移量
分页本质是两步:先查总数,再查本页数据。很多人直接用 SQL_CALC_FOUND_ROWS,但 MySQL 8.0+ 已弃用,且性能差;更稳的做法是单独发一条 COUNT(*) 查询。
关键逻辑在 PHP 里要手动算:$offset = ($page - 1) * $per_page,注意 $page 必须是整数且 ≥ 1,否则 $offset 可能为负或跳过第一页。
-
$page从$_GET['page']拿,必须用(int)强转或filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT)校验,不能直接拼进 SQL -
$per_page建议硬编码或白名单配置(如[10, 20, 50]),别让用户随意传 - 总页数用
ceil($total_count / $per_page),别用round或intval,否则最后一页可能被砍掉
SQL 里 LIMIT 的参数顺序和边界风险
LIMIT 在 MySQL 中是 LIMIT offset, length,不是 LIMIT length OFFSET offset(后者虽支持,但易读性差,且 PostgreSQL 才习惯这种写法)。
立即学习“PHP免费学习笔记(深入)”;
常见错误是把 $page * $per_page 当成 offset,结果第 1 页就从第 10 条开始,漏掉前 10 条。
- 正确写法:
SELECT * FROM article LIMIT <code>$offset,$per_page - 如果
$offset超过总记录数(比如总 105 条、每页 10 条、请求第 15 页),MySQL 不报错,但返回空数组——你要在 PHP 层判断$page > $total_pages并重定向到末页或 404 - 用 PDO 时,
bindValue传$offset和$per_page必须指定PDO::PARAM_INT,否则会被当字符串处理,导致 SQL 语法错误
前端分页链接怎么避免带脏参数
用户可能带着 ?sort=created&dir=desc 进入列表页,翻页时得保留这些参数,否则一翻页排序就丢了。
最简方案是用 http_build_query 动态生成 URL,而不是写死 ?page=2。
- 先取当前所有 GET 参数:
$params = $_GET; - 覆盖
page值:$params['page'] = $i; - 生成链接:
<a href="https://www.php.cn/link/323799e616cdbe8f6d89d6a253e8f0fc">第 <code>$i页 - 注意过滤掉空值或敏感键(如
token、debug),否则链接又长又不安全
为什么 COUNT(*) 和业务查询要共用同一套 WHERE 条件
很多人写两个 SQL:一个查 COUNT(*),一个查数据,但 WHERE 条件稍有不同(比如一个用了 status = 1,另一个漏了),就会导致总页数和实际可翻页数对不上。
最稳妥的是把 WHERE 条件抽成变量或函数,两边复用。
- 例如定义
$where = "WHERE status = 1 AND deleted = 0",然后分别用于:SELECT COUNT(*) FROM article <code>$where和SELECT * FROM article <code>$whereLIMIT ... - 如果条件含用户输入(如搜索关键词),记得统一做
LIKE转义或使用预处理,别在 COUNT 里漏掉escapeshellarg类操作 - 复杂查询(含 JOIN)时,COUNT(*) 可能比主查询慢——这时考虑加覆盖索引,或用缓存(如 Redis 存 5 分钟内的总数)
分页最难的不是写代码,而是让「总数」「当前页」「偏移量」「URL 参数」「SQL 条件」五者始终同步。少校验一个输入、少同步一处条件,翻到第三页就开始不对劲。



















