必须换分页方式的情况:数据量超10万行或查询含join/group by/distinct时,用simplepaginate()避免count(*)全表扫描;需跳转任意页时禁用simplepaginate();url页码键名、当前页码需通过paginate()第三、四参数显式指定;api响应须手动构造json结构,禁用自动序列化;大表分页务必确保order by字段有索引。

直接用 paginate() 就行,但选错方法或参数会导致查库慢、翻页错乱、前端拿不到数据——关键不在“会不会”,而在“什么时候该换别的分页方式”。
什么时候必须换 simplePaginate()
当你的查询带 JOIN、GROUP BY、SELECT DISTINCT,或者表数据量超过 10 万行时,paginate() 默认执行的 COUNT(*) 很可能全表扫描,接口响应从 200ms 拉到 2s+。这不是代码写得不对,是 MySQL 在 COUNT 阶段卡住了。
-
simplePaginate()只查LIMIT $perPage + 1条,不执行COUNT,速度接近原生LIMIT查询 - 它返回的是
SimplePaginator实例,没有total()、lastPage(),调用会报错 -
links()渲染出来只有「上一页 / 下一页」,没页码数字;API 响应里字段是nextPageUrl(驼峰)和hasMorePages,不是next_page_url - 前端做「加载更多」或「无限滚动」时,优先用它;后台管理列表要跳转任意页,就不能用
paginate() 的第三个和第四个参数怎么填
很多人只传一个数字,比如 paginate(15),结果在非标准 URL(如用 p 代替 page)或需要跳指定页时懵了。
- 第三个参数是 URL 中页码键名:
User::where('active', 1)->paginate(10, ['*'], 'p'),这样?p=3才能跳到第 3 页 - 第四个参数是手动指定当前页码:
->paginate(10, ['*'], 'page', 3),适合从缓存/队列取值后强制定位 - 如果用了
appends()传搜索参数,注意它会把所有$_GET参数都带上——敏感字段(如token)得先用request()->except('token')过滤再传 - 别在已调用
get()或toArray()的集合上调paginate(),会抛Call to undefined method ...::paginate()
API 返回分页数据,为什么前端拿到的是 HTML 字符串
因为 Laravel 默认对 LengthAwarePaginator 和 SimplePaginator 做了自动 JSON 序列化,但字段结构是为 Blade 设计的:links 是 HTML 字符串,data 是嵌套的二维数组,meta 字段缺失或命名不统一。
- 正确做法是手动构造响应体:
return response()->json(['data' => $users->items(), 'meta' => [...]]) -
$users->items()取出当前页纯数据数组,避免前端还要解构response.data.data - 必须显式提供
current_page、per_page、total(paginate下)、has_more(simplePaginate下)等字段,别依赖框架自动塞 - 如果业务允许且排序字段有唯一索引(如
id),改用cursorPaginate(),天然规避 offset 漂移和恶意per_page=999999攻击
自己拼数组分页?千万别
有人图省事,在控制器里先 $all = Model::get(),再用 array_slice() 切片,最后 new LengthAwarePaginator。这在几百条数据时没问题,一旦查出上万条,PHP 内存直接爆,MySQL 连接也容易超时。
- 真正需要手动构造分页器的场景只有两个:数据来自第三方 API(不是 DB)、或已缓存好全量数组且确认总量可控(
- 即使要手动分页,也得先限制源头:
$allData = cache()->remember('user_list', 3600, fn() => ExternalApi::fetch()) - 数据库分页永远走
paginate()/simplePaginate()/cursorPaginate(),它们生成的是带LIMIT和OFFSET(或游标条件)的 SQL,不是 PHP 层切数组 - 大表分页还卡?检查
ORDER BY字段有没有索引——没索引的ORDER BY created_at在千万级表里会让分页查询退化成文件排序











