Laravel 默认不支持游标分页,因paginate()依赖COUNT和OFFSET导致大数据量下性能骤降;游标分页通过排序字段值锚定下一页,避免深度扫描,需手动实现或使用laravel/cursor-paginator包。

游标分页不是 Laravel 默认提供的功能,paginate() 和 simplePaginate() 都基于 offset/limit,大数据量下性能会断崖式下降。真要游标分页,得自己实现或用第三方包。
为什么不能直接用 paginate() 做游标分页
Laravel 的 paginate() 必须执行 COUNT 查询来算总页数,且依赖 OFFSET 跳转——当页码很大(比如第 10000 页)时,MySQL 要扫描前 150000 行才能拿到数据,I/O 和 CPU 开销爆炸。游标分页绕开 offset,靠「上一页最后一条记录的排序字段值」作为下一页起点,天然避免深度扫描。
-
paginate(15)在 page=10000 时实际执行LIMIT 149985, 15,效率取决于索引覆盖和排序稳定性 - 没有内置
cursorPaginate()方法;Laravel 官方至今未提供游标分页原生支持(截至 2026 年 4 月) - 试图对
LengthAwarePaginator或SimplePaginator“魔改”实现游标逻辑,大概率破坏分页器契约,导致links()、hasMorePages()等行为异常
手动实现游标分页的关键三步
核心是:用唯一、有序、非空的字段(通常是 id 或 created_at)做游标锚点,每次查询只取“比上一页末尾值更大”的一批记录。
- 前端首次请求不带游标参数,后端查第一页:按排序字段倒序取前
$perPage条,例如SELECT * FROM posts ORDER BY id DESC LIMIT 15 - 返回响应时,把最后一条记录的
id(或组合字段如id, created_at)作为next_cursor字段传给前端 - 前端下次请求带上
?cursor=12345,后端构造查询:SELECT * FROM posts WHERE id < 12345 ORDER BY id DESC LIMIT 15(注意是<,不是<=,避免重复) - 必须为游标字段加数据库索引,否则 WHERE + ORDER BY 会全表扫描;若用复合游标(如
created_at, id),索引需匹配顺序
laravel/cursor-paginator 包怎么用
社区最成熟的方案是 laravel/cursor-paginator(由 Laravel 核心贡献者维护),它提供 CursorPaginator 类,行为接近原生分页器但无 offset。
- 安装:
composer require laravel/cursor-paginator - 用法示例(控制器中):
$cursor = $request->input('cursor'); $posts = Post::where('status', 'published') ->orderBy('id', 'desc') ->cursorPaginate(15, ['*'], 'cursor', $cursor); - 返回对象是
CursorPaginator实例,有items()、nextCursor()、previousCursor()、hasMorePages()等方法,但没有total()或lastPage()—— 这是游标分页的设计使然 - Blade 中不能直接用
$posts->links(),因为默认视图依赖总页数;需手写链接逻辑或自定义视图,例如:<a href="?cursor={{ $posts->nextCursor() }}">下一页</a>
API 场景下容易忽略的边界问题
游标分页在 API 中更常见,但几个细节不处理好就会翻车:
- 游标值必须 URL-safe,
base64_encode()或urlencode()处理后再传给前端,尤其当游标是时间戳+ID组合时 - 不能信任客户端传来的游标值,必须校验格式并防 SQL 注入;建议用
Post::query()->whereRaw('id > ?', [$cursor])而非字符串拼接 - 排序字段必须严格唯一且不可为空,否则会出现漏数据或重复;若业务允许,优先用自增
id,慎用created_at(毫秒级不保证唯一) - 前端“加载更多”时,即使
hasMorePages()为 true,也要检查返回的data数组长度是否为 0,防止无限请求空结果
游标分页真正难的不是代码怎么写,而是设计阶段就要想清楚:排序依据是否稳定、游标如何编码传输、前端如何管理 cursor 状态、缓存策略怎么适配——这些一旦定错,后期几乎无法平滑迁移。


















