最简分页逻辑:校验页码和每页数为正整数,偏移量=($page-1)*$per_page,sql用limit $per_page offset $offset;查$per_page+1条判断是否有下一页,limit参数需白名单校验不可绑定。

PHP 用 mysqli_query + LIMIT 做分页,最简可行写法
直接上手就能跑通的分页逻辑:查总数、算偏移量、拼 LIMIT。别先搞框架封装,先确保单次请求能返回正确第 N 页数据。
常见错误是把 OFFSET 算错,比如页码从 1 开始但偏移量没减 1,结果第一页就漏掉首条;或者没校验 $page 和 $per_page 是否为正整数,导致 SQL 报错或越界查询。
-
$page = max(1, (int)$_GET['page'] ?? 1);—— 强制转整型并兜底 -
$per_page = 20;—— 固定每页条数比动态传入更安全(避免传per_page=999999) -
$offset = ($page - 1) * $per_page;—— 这个减 1 是关键,漏了就全错 - SQL 拼写必须是
SELECT * FROM table LIMIT $per_page OFFSET $offset,顺序不能反
为什么不能只靠 COUNT(*) 查总页数?
查总页数本身不慢,但当表有几百万行时,SELECT COUNT(*) FROM huge_table 会锁表或严重拖慢主查询,尤其在高并发下。用户点第 100 页时,你未必真需要知道“总共 1024 页”——显示“下一页”和“尾页”按钮足够。
更务实的做法是查 $per_page + 1 条数据:如果拿到 21 条,说明还有下一页;如果只拿到 20 条,基本可判定是最后一页(除非刚删数据)。这样省掉一次全表计数。
立即学习“PHP免费学习笔记(深入)”;
本书是全面讲述PHP与MySQL的经典之作,书中不但全面介绍了两种技术的核心特性,还讲解了如何高效地结合这两种技术构建健壮的数据驱动的应用程序。本书涵盖了两种技术新版本中出现的最新特性,书中大量实际的示例和深入的分析均来自于作者在这方面多年的专业经验,可用于解决开发者在实际中所面临的各种挑战。 本书内容全面深入,适合各层次PHP和MySQL开发人员阅读,既是优秀的学习教程,也可用作参考手册。
- 查数据时用
LIMIT 21,不是LIMIT 20 - PHP 中用
array_slice($rows, 0, 20)截出要展示的 20 条 - 判断
count($rows) > 20决定是否渲染“下一页”链接
PDO::prepare 防注入,但 LIMIT 参数不能直接绑定
LIMIT 后的数字不是普通参数,PDO 不允许用占位符绑定,否则报错 SQLSTATE[HY093]: Invalid parameter number。必须手动过滤后拼进 SQL 字符串。
别信“用 intval() 就绝对安全”,因为 intval('1abc') 返回 1,看似无害,但若攻击者构造 page=1%00--%20(URL 编码),某些老旧 PHP 版本可能截断失败。稳妥做法是白名单校验或强类型断言。
- 用
filter_var($offset, FILTER_VALIDATE_INT, ['options' => ['min_range' => 0]]) !== false更严谨 - 或直接
if ($offset - 拼 SQL 时只用已验证变量:
"LIMIT $per_page OFFSET $offset",不加引号
MySQL 8.0+ 可用 ROW_NUMBER(),但多数场景没必要
用窗口函数做分页看起来高级,比如 SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY id) AS rn FROM t) t2 WHERE rn BETWEEN 101 AND 120,但它会强制扫描全表排序,性能远不如 LIMIT OFFSET,尤其跳到大页码时。
真正该换方案的时候是:需要稳定按时间/热度排序且数据实时增删频繁(OFFSET 越大越不准),这时应改用游标分页(WHERE created_at ),而不是硬上窗口函数。
-
LIMIT OFFSET适合后台管理、页码明确的场景 - 游标分页适合 Feed 流、评论列表等强调“最新”且不允许跳页的场景
- 别为了“技术新”在简单列表里强行套
ROW_NUMBER()
事情说清了就结束










