OFFSET偏移量固定为($page-1)*$limit,页码从1开始且需校验;ThinkPHP6中offset()会被paginate()重置,应改用page()和listRows控制分页。

OFFSET 偏移量的计算公式是固定的
OFFSET 不是随便填的数字,它必须严格等于 ($page - 1) * $limit。页码从 1 开始,不是从 0;每页条数 $limit 必须是正整数;$page 必须做过校验(比如强制转为 int、max(1, $page))。
常见错误是直接用用户传入的 $_GET['page'] 做乘法,没过滤或没兜底——比如传入 page=abc 或 page=-5,会导致 $offset 变成 0 或负数,SQL 报错或数据错乱。
-
$page必须用(int)强转,再用max(1, $page)保证最小为 1 -
$limit同样要校验,建议白名单控制(如只允许 10/20/50),防止恶意传limit=999999 - 排序字段必须明确(如
ORDER BY id DESC),否则LIMIT + OFFSET结果不可预测
ThinkPHP 6 中 offset() 调用会被 paginate() 清空
如果你写的是 $query->offset(50)->limit(10)->paginate(10),那 offset(50) 完全无效。ThinkPHP 6.1+ 的 paginate() 内部会重置查询条件,包括丢弃你手动设的 offset 和 limit。
真正起作用的是页码和每页条数:用 paginate(10, false, ['page' => 7]) 指定第 7 页,框架自动算出 OFFSET = (7 - 1) * 10 = 60,并生成正确 SQL。
立即学习“PHP免费学习笔记(深入)”;
- 不要混用
offset()和paginate() - 想跳到“第 100 条开始”,先换算成页码:
ceil(100 / $limit),再传给page参数 -
paginate()默认读取 GET 参数page,所以 URL 里带?page=7就行,不用手算 offset
MySQL 5.7 以前不支持 OFFSET 语法,容易报错
部分老环境(尤其 MySQL 5.6 或更低)不识别 LIMIT 10 OFFSET 50 这种写法,会抛出 SQLSTATE[42000]: Syntax error。此时得改用 LIMIT 50, 10 格式(即 LIMIT offset, count)。
PDO 预处理本身不处理语法差异,所以你要根据数据库版本决定写法:
- 确认 MySQL 版本:
SELECT VERSION(); - 若低于 5.7,SQL 写成
LIMIT :offset, :limit,绑定时注意顺序 - 若用 ThinkPHP,可通过配置
'sqlsrv' => false或自定义查询构造器绕过默认语法
深度分页时 OFFSET 性能崩塌,该换游标了
当 OFFSET 超过 10 万,MySQL 实际会扫描前 N 行再丢弃,查询变慢甚至锁表。这不是 PHP 能优化的,是 SQL 层面的固有缺陷。
这时别硬撑,换成基于主键或时间戳的游标分页:
- 查第一页:
SELECT * FROM posts WHERE status = 1 ORDER BY id DESC LIMIT 20 - 下一页用最后一条的
id:SELECT * FROM posts WHERE status = 1 AND id - 不再依赖
COUNT(*)和总页数,前端只提供“下一页”按钮,不显示页码
最容易被忽略的一点:游标分页无法复用 paginate() 的渲染逻辑,render() 生成的 HTML 分页栏会失效——你得自己拼链接,传游标值,后端验证游标合法性。



















