正确计算 offset 和 limit:$offset = ($page - 1) * $per_page,$limit = $per_page;需校验 $page 范围、sql 必带稳定 order by、url 参数须 htmlspecialchars 编码、总数需独立 count 查询并校验页码越界。

PHP 实现分页,核心不是写多少行代码,而是正确计算 offset 和 limit,并确保 SQL 查询和 URL 参数协同一致。手写分页不难,出错多因忽略边界条件或数据一致性。
怎么算出正确的 offset 和 limit
分页本质是“跳过前 N 条,取 M 条”。关键变量只有三个:$page(当前页码)、$per_page(每页条数)、$total(总记录数)。必须从请求中获取 $page,且强制转为整型并做范围校验:
-
$page = max(1, (int)$_GET['page'] ?? 1);—— 防止负数、字符串、空值 -
$offset = ($page - 1) * $per_page;—— 第一页 offset 是 0,不是 1 -
$limit = $per_page;—— 不建议动态改 limit,否则页码逻辑会混乱
SQL 查询必须带 ORDER BY 才能分页可靠
没有 ORDER BY 的分页查询结果不可预测,尤其在数据有重复排序字段时,同一条记录可能出现在不同页。MySQL 8.0+ 虽支持窗口函数,但传统 LIMIT 分页仍依赖稳定排序:
- 错误写法:
SELECT * FROM article LIMIT ?, ? - 正确写法:
SELECT * FROM article ORDER BY id DESC LIMIT ?, ? - 如果按时间排序,
created_at有重复,建议追加id:ORDER BY created_at DESC, id DESC
怎么生成安全的分页链接(不含 XSS 风险)
URL 中的 page 参数直接拼进链接前,必须用 htmlspecialchars() 编码,否则用户传入 page=1"><script>alert(1)</script> 会导致前端 XSS:
立即学习“PHP免费学习笔记(深入)”;
- 不要这样:
<a href="?page=<?=%24page+1?>">下一页</a> - 应该这样:
<a href="?page=<?=htmlspecialchars(%24next_page)?>">下一页</a> - 所有 GET 参数(如搜索关键词
$q)都要重新带入链接:?q==urlencode($q)?>&page==htmlspecialchars($page)?>
总数查询不能省,但可以优化
显示“共 XX 条”和计算总页数必须查总数,但 COUNT(*) 全表扫描代价高。常见误判是用 mysql_num_rows() 取结果集行数代替总数 —— 这只返回本页条数,不是全量。
- 简单场景:直接
SELECT COUNT(*) FROM article WHERE ...,WHERE 条件必须和分页主查询完全一致 - 大数据量:可考虑缓存总数(如 Redis 存 5 分钟),或改用估算(
SHOW TABLE STATUS的Rows字段误差大,慎用) - 极端情况(如百万级文章+高频更新):放弃精确总数,只提供“下一页”和“上一页”按钮,不显示页码和总条数
真正容易被绕过的点是:没验证 $page 是否超过最大页码。比如总 102 条、每页 10 条,最大页是 11,但用户访问 ?page=999 时,SQL 仍会执行 LIMIT 9980,10 —— MySQL 不报错,只是返回空结果集,页面变空白。这个判断必须手动做:if ($page > $max_page) { header('Location: ?page=' . $max_page); exit; }










