order by 必须在 limit 前,否则分页错乱;多字段排序提升稳定性;禁用 order by rand();sort/order 需白名单校验并反引号包裹;大数据量应改用游标分页,依赖唯一非空索引字段。

ORDER BY 必须写在 LIMIT 前面,否则数据会乱
分页后数据跳变、重复或遗漏,八成是因为 SQL 里漏了 ORDER BY,或者它被写在 LIMIT 后面。数据库不保证无序查询的返回顺序——哪怕你本地测试时“看起来正常”,换服务器、加索引、数据量一涨就崩。
-
SELECT * FROM users LIMIT 10 OFFSET 20 ORDER BY id DESC是错的:ORDER BY 被忽略或执行时机错误 - 正确写法只能是:
SELECT * FROM users ORDER BY id DESC LIMIT 10 OFFSET 20 - 多字段排序要小心稳定性:
ORDER BY status ASC, created_at DESC比单用created_at更抗重复值,但前提是status和created_at组合能唯一确定一行 - 绝对别用
ORDER BY RAND()分页:每次LIMIT都是全新随机抽样,翻页即重抽,根本不是“下一页”
sort 和 order 参数必须白名单校验
用户传来的 sort=title&order=desc 看似可控,但攻击者可能拼出 sort=id; DROP TABLE users-- 或绕过注入检测的畸形字段名。PHP 层不能信任任何未经筛选的 $_GET['sort']。
- 硬编码白名单:
$allowed_sorts = ['id', 'title', 'email', 'created_at']; - 校验逻辑必须严格:
$sort = in_array($_GET['sort'] ?? '', $allowed_sorts) ? $_GET['sort'] : 'id';,不能只用isset()或!empty() -
order只接受asc或desc,其他值一律 fallback 到默认方向 - 字段名直接拼进 SQL 前,必须用反引号包裹:
`{$sort}`,防止关键字冲突(比如字段叫order)
大数据量下 OFFSET 性能断崖式下降
当 OFFSET 超过 10 万行,MySQL 往往要扫描并丢弃前面所有匹配行,查询从毫秒级变成秒级甚至超时。这不是代码写得不够好,是 SQL 执行模型本身的瓶颈。
- 游标分页(Keyset Pagination)是更优解:用上一页最后一条的
id作为下一页起点,例如WHERE id > 12345 ORDER BY id ASC LIMIT 20 - 游标要求排序字段严格唯一、非空、有索引——自增主键最稳妥;时间戳需配合唯一辅助字段(如
id)避免并发插入导致重复 - 无法跳转任意页码(比如“第 200 页”按钮),但可通过“首页/末页+上/下一页”覆盖 95% 场景
- 前端需把游标值(如
cursor=12345)和方向(direction=next)一起带在链接里,不能混用page和cursor
生成分页链接时必须保留 sort/order 参数
用户点完“按邮箱升序”再点第 3 页,结果却按默认 id DESC 查第 3 页——这种体验断裂,往往只是因为生成页码 HTML 时没合并当前排序参数。
- 用
http_build_query(array_merge($_GET, ['page' => $i]))构造链接,比手拼字符串安全且自动处理空值 - 注意清理非法键:
array_filter($_GET, 'is_string')可过滤掉数组型或 null 值参数,避免http_build_query报 warning - 排序按钮本身也要是完整链接:
@#@#@#@#@#@#@#@#@#@0,而不是只改 hash 或 JS 拦截 - 如果用了游标分页,
page参数应被移除,链接只保留cursor和direction,避免语义混淆
实际项目里最容易被忽略的,是排序字段的索引覆盖和游标字段的唯一性保障。即使 SQL 写对了、参数校验也全了,缺了这两条,分页要么慢得没法用,要么在高并发写入时悄悄漏数据。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











