mysql limit分页正确写法是limit offset, length,如第1页为limit 0,10,第2页为limit 10,10;需校验page参数防溢出,大offset性能差应改用游标分页,并避免sql_calc_found_rows。

MySQL LIMIT分页的正确写法是 LIMIT offset, length
直接写 LIMIT 10, 20 表示跳过前 10 条,取接下来的 20 条。注意:第一个数是偏移量(从 0 开始),第二个是条数,不是“第几页”。很多人误以为 LIMIT 1, 10 是第一页,结果漏掉第 1 条——实际它跳过了第 1 条,取的是第 2–11 条。
常见错误现象:SELECT * FROM user LIMIT 1, 10 返回第 2–11 行,但业务上想要第 1–10 行,应写成 LIMIT 0, 10 或更常见的 LIMIT 10(省略 offset 时默认为 0)。
- 第 1 页(页码从 1 开始)对应
LIMIT 0, 10 - 第 2 页对应
LIMIT 10, 10 - 第
$page页对应LIMIT ($page - 1) * $per_page, $per_page
PHP 中计算 offset 容易整数溢出或负值
用户传参 $_GET['page'] 可能是字符串、空值、负数或超大数。不校验直接参与计算会导致 SQL 错误或越界查询。
- 必须用
(int)强转并做范围限制:$page = max(1, (int)$_GET['page']); -
$offset = ($page - 1) * $per_page;前先检查$per_page是否合法(比如限定 5–100) - 避免
$offset超过表总行数导致全表扫描——高偏移量性能极差,后面会提到
LIMIT 大 offset 查询慢?这是 MySQL 的固有缺陷
执行 LIMIT 100000, 20 时,MySQL 仍需定位到第 100001 行,内部会扫描前 100020 行(甚至更多),无法跳过。这不是 PHP 写法问题,而是 B+ 树索引不支持“反向跳转”。
立即学习“PHP免费学习笔记(深入)”;
真实场景中,当 $offset > 10000 就该警惕。替代方案不是换 PHP 逻辑,而是换查询策略:
- 用游标分页(cursor-based pagination):基于上一页最后一条记录的
id或时间戳,如WHERE id > 12345 ORDER BY id LIMIT 20 - 对高频分页字段建联合索引,例如
INDEX(status, created_at, id),让排序+分页走索引覆盖 - 避免
SELECT *,只查必要字段,减少回表和传输开销
为什么不要用 SQL_CALC_FOUND_ROWS 获取总页数
这个语句曾被用来在分页时顺带统计总数,比如:SELECT SQL_CALC_FOUND_ROWS * FROM t LIMIT 10; SELECT FOUND_ROWS();。但它会让 MySQL 扫描全表(即使加了 LIMIT),且在 MySQL 8.0.17+ 已被废弃。
更可靠的做法是单独查总数,但要注意缓存与一致性权衡:
- 用
SELECT COUNT(*) FROM t WHERE ...(条件必须和分页查询完全一致) - 如果数据变动不频繁,把总数缓存到 Redis,过期时间设为几分钟
- 对超大表,可改用估算值:
SHOW TABLE STATUS LIKE 't'查Rows字段(误差可能达 40%)
真正难的不是写出 LIMIT,而是在数据量增长后,发现第 500 页加载要 3 秒——这时候再回头改游标或索引,远比一开始想清楚分页模型更费劲。











