正确用法是先调用 with() 构建查询,再链式调用 paginate(),laravel 会自动处理 count 和数据查询;错误写法如 with()->get()->paginate() 会导致内存溢出,且 with() 中 select() 必须包含外键和主键字段。

paginate() 和 with() 一起用会报错:Call to undefined method Illuminate\Database\Eloquent\Builder::paginate()
常见错误是直接在 with() 后链式调用 paginate(),比如:User::with('posts')->paginate(10)。这在 Laravel 8+ 里看似能跑,但实际会触发 N+1(如果没写对)或分页失效(比如总数不准)。根本原因是 with() 返回的是 Builder,而 paginate() 是 QueryBuilder 的方法——但关键在于:**预加载必须在分页查询执行前完成,且不能干扰分页的 count 查询逻辑**。
- 正确姿势是先构建带关联的查询,再调用
paginate(),Laravel 内部会自动处理两次查询(count + 数据) - 别手动写
->get()->paginate(),那会把整个数据集拉进内存再分页,彻底崩掉性能 - Laravel 9+ 对
withCount()和withSum()等聚合预加载也支持原生分页,但需注意它们默认不参与 count 查询优化
with() 预加载字段限制:select() 必须包含外键和主键
想节省带宽只查部分字段?可以,但 select() 里漏掉关联所需的字段,分页时就会查不到关联数据,甚至报错 Column not found。典型场景是 Post::with(['user' => function ($q) { $q->select('id', 'name'); }])->paginate(10) —— 这会失败,因为没选 user_id(外键)或没指定表名前缀。
- 必须显式包含外键字段,例如:
$q->select('id', 'name', 'email', 'id')(注意:id是 users 表主键,user_id是 posts 表外键,两者都要有上下文) - 更安全写法是加表名前缀:
$q->select('users.id', 'users.name', 'users.email') - 如果用了
selectRaw()或复杂子查询,分页的 count 查询可能无法自动推导,导致总数错误
嵌套预加载 + 分页:with(['posts.comments']) 的坑在哪
嵌套层级一深,分页就容易慢,甚至 MySQL 报 SQLSTATE[HY000]: General error: 1390 Prepared statement contains too many placeholders。这不是 Laravel 的 bug,而是底层 PDO 绑定参数超限(尤其 MySQL 5.7 默认 max_allowed_packet 小)。
- 嵌套预加载本质是多个 JOIN 或独立查询,Laravel 默认用“分离查询”策略(N+1 优化版),但分页时仍需为每层做一次额外查询
- 避免
with(['posts.tags', 'posts.comments.user'])这种三级以上嵌套,优先拆成多次分页请求或改用 API 资源懒加载 - 如果坚持嵌套,务必给每层加
select()限制字段,否则数据膨胀极快
分页后想获取关联模型的总数(比如用户发了多少文章)
很多人以为 withCount('posts') 和 paginate() 一起用就能拿到每个用户的 post_count 字段——确实可以,但要注意:这个计数是「按分页结果里的用户」分别统计的,不是全量用户的数据。如果你在控制器里写了 User::withCount('posts')->paginate(10),返回的每个 User 实例都有 $user->posts_count,但这个值只反映该用户自己的文章数,和分页无关。
-
withCount()不影响分页总数(即总用户数),它只是附加字段 - 若要统计「当前这页用户共发多少文章」,得用集合操作:
$users->sum('posts_count'),但这不是数据库层面聚合,慎用于大数据集 - 需要全局聚合(如所有用户文章总数)?别塞在分页查询里,单独走
Post::count()
预加载分页最易被忽略的一点:关系定义里有没有加 ->where() 或 ->orderBy()?只要在闭包里写了这些,就可能让分页的 count 查询失效(因为 count 不会执行 order 或 where 条件),导致分页总数和实际数据行数对不上。这种情况没有报错,只有数据错位,排查起来特别花时间。











