simple模式跳过count但保留offset/limit,适合中等数据量且需页码栏;游标分页抛弃offset和总数,仅靠排序字段定位,适合百万级+无限滚动场景。

simple 模式和游标分页不是“选一个更好”,而是解决不同问题的两种手段:simple 模式跳过 COUNT 但保留 offset/limit,适合中等数据量+需保持页码栏;游标分页彻底抛弃 offset 和总数,只靠排序字段定位,适合百万级以上+无限滚动场景。用错地方,性能反而更差。
simple 模式什么时候能用、什么时候不能用
simple 模式本质是 paginate(['simple' => true]),它让 ThinkPHP 只执行一次查询:SELECT * FROM table WHERE ... LIMIT 21,取 21 条来判断“是否有下一页”,不查 COUNT(*)。
- 适用场景:表数据几十万、WHERE 条件有覆盖索引、前端仍需显示「上一页 / 下一页」按钮(不要求总页数)
- 不适用场景:前端强依赖「共 X 条」「跳转末页」;或
LIMIT 20 OFFSET 500000已明显变慢——simple 不解决大 offset 问题,只是省了 COUNT - 注意:启用后
appends()失效,必须用数组参数的query键传参,否则翻页丢失搜索条件 - 渲染时
$list->render()只输出两个按钮,不会生成页码数字;若强行在模板里调$list->lastPage()会触发额外 COUNT 查询,白开 simple
游标分页必须满足的硬性条件
ThinkPHP 的 cursorPaginate() 不是加个方法就能提速,它对数据结构和查询方式有强制约束:
- 排序字段必须为
NOT NULL、单调、有索引——id主键最稳妥;create_time要设默认值且防并发重复 - 必须显式写
order('id asc')或order('id desc'),且方向要和游标条件匹配:where('id', '>', last_id)对应asc,where('id', ' 对应 <code>desc - 不支持关联查询(
with())、聚合字段、whereRaw()干扰主键比较逻辑,否则自动退化为普通分页 -
cursorPaginate(20)返回结果里,下一页要用的不是last_id,而是$list->next_cursor字段(可能是数组),前端必须原样透传
别把游标分页当成 paginate() 的快捷参数
游标分页不是 paginate() 的增强版,它是另一套查询范式。常见误用包括:
- 在控制器里写
User::cursorPaginate(20)->where(...)——cursorPaginate()是终结方法,链式调用 where 无效 - 前端首次请求传
?cursor=0,但数据库 id 从 1 开始,导致第一条数据被跳过;正确做法是首次不带 cursor 或传空 - 用
create_time游标却没处理同一秒多条的情况,结果靠WHERE create_time > '2024-05-01 10:00:00'查不到数据,必须补上id作为第二排序字段:order('create_time desc, id desc') - 在事务里用游标分页但没消费完全部结果,MySQL 连接可能卡住,尤其配合长耗时业务逻辑时
真正决定性能的从来不是框架方法名,而是最终生成的 SQL 是否能走索引、是否避免全表扫描。simple 模式省一次 COUNT,游标分页砍掉 COUNT + 大 offset,但两者都要求你先确认 WHERE 和 ORDER 字段已有合适索引——没索引,换什么分页都慢。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











