MySQL分页需用LIMIT offset,row_count配合ORDER BY确保排序稳定,offset从0开始;PHP须校验页码为正整数并硬编码进SQL,避免注入;大偏移量应改用游标分页。

MySQL LIMIT 子句怎么写才不翻页错乱
分页错乱,八成是 LIMIT 的两个参数用反了,或者没配合 ORDER BY。MySQL 的 LIMIT offset, row_count 要求必须先排好序,否则同一页可能漏数据或重复出现。
常见错误现象:SELECT * FROM posts LIMIT 20,10 返回的第3页内容和第2页有重叠,或者跳过某几条记录。
-
offset是从 0 开始算的,第1页应为LIMIT 0,10,第2页是LIMIT 10,10,别手抖写成LIMIT 1,10 - 必须带确定性排序,比如
ORDER BY id ASC;用ORDER BY created_at要小心时间相同的情况,建议补上id作二级排序:ORDER BY created_at DESC, id DESC - 如果表里
id不连续(删过记录),不能靠WHERE id > last_id简单替代LIMIT,除非你明确在做游标分页
PHP 怎么安全计算当前页码和总页数
用户传来的 page 参数不能直接进 SQL,得先校验、截断、转整型——否则 SQL 注入或崩溃只在一线之间。
使用场景:URL 形如 /list.php?page=3,但用户可能输 page=abc、page=-1、page=9999999。
立即学习“PHP免费学习笔记(深入)”;
- 用
filter_input(INPUT_GET, 'page', FILTER_SANITIZE_NUMBER_INT)拿值,再强制转(int),避免字符串注入 - 最小页码设为 1:
$page = max(1, $page); - 查总数别用
COUNT(*)全表扫,如果只是展示“大概多少页”,可加SQL_CALC_FOUND_ROWS(但 MySQL 8.0+ 已弃用),更稳的做法是单独查一次SELECT COUNT(*),并加缓存 - 总页数计算:$total_pages = ceil($total_count / $per_page); 注意除零和浮点误差,
$per_page必须大于 0
为什么用 PDO 预处理能防分页注入
因为 LIMIT 的参数不支持预处理占位符(? 或 :name),所以很多人误以为 PDO 对分页无效——其实关键在「谁拼进去」。
错误写法:$sql = "SELECT * FROM users LIMIT {$offset},{$limit}"; —— 这里 {$offset} 是裸变量,危险。
- PDO 防注入的前提是:所有用户输入只进
bindValue()或execute()数组,绝不拼进 SQL 字符串 -
LIMIT参数必须由 PHP 校验后硬编码进 SQL,比如:"LIMIT ".(int)$offset.",".(int)$limit,而不是拼接未过滤的$_GET['page'] - 真正该用预处理的是 WHERE 条件部分,比如搜索关键词:
WHERE title LIKE ?,这部分和分页逻辑要拆开处理 - 如果你用的是 Laravel 或 ThinkPHP,它们的
paginate()内部已做这些检查,但底层仍是上述逻辑
大偏移量(如 LIMIT 100000,20)为什么慢
MySQL 要先扫出前 100000 行,再扔掉,只留后面 20 行——不是跳着读,是真的一行行过。
性能影响:当 offset > 10000,响应时间可能从 10ms 涨到 2s+,DB CPU 直线上升。
- 替代方案是游标分页(cursor-based pagination):用上一页最后一条的
id做条件,比如WHERE id - 游标分页不能跳页,但适合信息流、日志等场景;传统页码分页(page-based)适合后台管理,需搭配缓存或延迟关联优化
- 如果非要用大 offset,确保
ORDER BY字段有索引,且类型匹配(比如id是BIGINT,就别用字符串比较) - 某些 ORM(如 Doctrine)提供
byId()分页封装,本质还是帮你转成游标逻辑
分页看着简单,但 offset 错一位、order by 少一个字段、page 参数没 cast 成 int,三个小点凑一块,线上就出数据对不上、接口超时、被刷库的问题。实际项目里,宁可多写两行校验,也别信“用户不会乱输”。











