mysql limit 的 offset 是偏移量而非页码,正确计算为($page-1)*$size;offset过大导致性能骤降;并发下易重复或遗漏,宜用游标分页;php中须(int)强转并校验范围,防sql注入与越界。

MySQL LIMIT 用法和 offset 容易错在哪
分页的核心就是 LIMIT,但它不是“取第几页”,而是“跳过 N 条后取 M 条”。很多人写成 LIMIT $page, $size,结果第一页就崩——因为 MySQL 的 LIMIT offset, length 第一个参数是偏移量,不是页码。
- 页码从 1 开始,但
offset = ($page - 1) * $size才对;写成$page * $size会每页少查一条 -
offset过大时(比如几百万行后),MySQL 仍要扫描前面所有行,性能断崖式下降 - 如果数据有并发增删,靠
OFFSET分页会出现重复或遗漏(游标分页更稳,但需有序主键)
PHP 中拼接分页 SQL 的安全写法
直接字符串拼接 $page 和 $size 是 SQL 注入高危操作。哪怕你“确定是数字”,也得走类型强转或 PDO 绑定——因为 PHP 的弱类型可能让 "123abc" 变成 123,但攻击者可以构造 "123 OR 1=1" 绕过判断。
- 必须用
(int)强转页码和每页条数:$page = (int)$_GET['page'] ?: 1;,$size = min((int)$_GET['size'], 100);(防爆破) - SQL 中只允许这两个变量进
LIMIT,且必须放在最后、不参与 WHERE - 示例正确写法:
$sql = "SELECT id,name FROM user WHERE status=1 ORDER BY id DESC LIMIT " . ($page-1)*$size . ", " . $size;
怎么算总页数才不查两次数据库
很多代码先 SELECT COUNT(*) 再 SELECT ... LIMIT,看似合理,但并发写入时,两次查询之间数据可能变化,导致页码错乱或最后一页空。
- 如果业务允许“近似总数”,可用
SHOW TABLE STATUS LIKE 'user'查Rows字段(MyISAM 准,InnoDB 是估算) - 更靠谱的是用
SQL_CALC_FOUND_ROWS(已弃用,8.0+ 不支持),或改用子查询加COUNT(*) OVER()(MySQL 8.0+) - 实际项目中,多数场景只需“有没有下一页”:查
$size + 1条,够 $size+1 就显示下一页按钮,否则不显示
前端传参和边界处理的硬坑
用户手动改 URL 里的 page=999999 很常见,不拦住就会查出空结果甚至拖垮数据库。
立即学习“PHP免费学习笔记(深入)”;
- 页码必须校验范围:
if ($page ,同时建议设上限(如 <code>max_page = 1000) - 每页数量也要限制(
1 ),防止 <code>size=10000把内存打满 - 不要依赖
$_GET原值:$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;,否则未传page时(int)null是 0,offset = -10直接报错 - 错误提示别暴露细节,
400 Bad Request比 “offset 不能为负” 更安全
真正难的不是写出能跑的分页,而是想清楚:这里能不能接受数据轻微偏移?要不要为高偏移量加缓存?游标分页的排序字段有没有唯一性保证?这些比语法细节更容易被忽略。











