laravel分页应依场景选paginate()、simplepaginate()或cursorpaginate():前者适合小数据量全量统计,后者规避offset漂移;大表联查时count易成瓶颈,可改用simplepaginate()跳过总数计算,或手动构造lengthawarepaginator。

Laravel 的分页不是靠手动计算偏移量实现的,而是通过 paginate() 方法自动处理查询、总数统计和 URL 生成——只要数据量不大(比如单表万级以内),直接用它最稳;但一旦涉及复杂联表、大表 count 或自定义查询,就容易卡顿或翻页错乱。
为什么 paginate() 有时会慢?
因为默认会执行两条 SQL:一条带 LIMIT 和 OFFSET 取数据,另一条加 COUNT(*) 算总页数。在大表或含 JOIN/GROUP BY 的查询里,COUNT(*) 可能全表扫描。
- 查 100 万行订单关联用户信息时,
->paginate(15)可能卡住 2 秒以上 -
withCount()或selectRaw()混用时,Eloquent 可能无法正确构造 count 查询,抛出SQLSTATE[42000]: Syntax error - 使用
distinct后没配groupBy,paginate()的 count 会报错或结果不准
simplePaginate() 适合什么场景?
它只执行带 LIMIT 的查询,不统计总数,因此无 count 开销,速度接近原生 limit 查询——适用于「只提供下一页/上一页按钮」的列表(比如日志流、消息 Feed)。
- 前端不需要显示「共 XX 条,第 X 页/共 Y 页」时,优先用
simplePaginate() - 返回结果是
LengthAwarePaginator(含总数) vsSimplePaginator(无总数),模板里调$users->lastPage()会报错 - URL 参数名默认仍是
page,但appends()行为一致,比如->appends(['search' => 'john'])仍可用
如何手动控制分页参数?
默认从 $_GET['page'] 读取当前页码,但可强制指定;同时支持修改每页条数、查询参数键名、甚至游标式分页(需配合 cursorPaginate())。
- 跳转到第 3 页:
User::where('active', 1)->paginate(10, ['*'], 'page', 3) - 把参数名从
page改成p:->paginate(10, ['*'], 'p') - 游标分页(避免 offset 漂移):
->cursorPaginate(20),要求排序字段有唯一索引(如id或created_at,id) - 禁用查询字符串拼接(比如防止重复添加
page):用->withQueryString()->withoutQueryString('page')
自定义分页器或绕过 Eloquent 分页
当必须用原生 SQL、视图、或 Elasticsearch 结果做分页时,Eloquent 的 paginate() 不适用,得自己构造 LengthAwarePaginator 实例。
- 先查数据(例如 DB::select(...)),再单独查总数(可能用缓存或估算值),最后 new
LengthAwarePaginator($items, $total, $perPage, $currentPage) - 注意:$items 必须是
Collection或数组,不能是PDOStatement或原始结果集 - URL 生成要手动处理,
LengthAwarePaginator的url()方法依赖Request,若脱离 HTTP 上下文(如队列中生成链接),会失效
真正难的不是调用 paginate(),而是判断什么时候不该用它——count 性能、排序稳定性、前端交互需求,这三点没理清,光改每页数量只是掩耳盗铃。











