with()能解决N+1问题,因其将关联查询合并为1~2次SQL:先查主表,再用主表ID集合一次性查关联数据,最后内存映射。如查100篇文章及作者,传统需101条SQL,with('author')仅需2条。

ThinkPHP 中的 N+1 查询问题,本质是主表查一次、每条记录再为关联数据单独发起一次查询,导致数据库请求暴增。用 with() 预载入能将关联查询合并为 1~2 次 SQL,显著提升性能。
为什么 with() 能解决 N+1
with() 不是在 PHP 循环里逐条查关联,而是先查主表,再根据主表 ID 集合一次性查出所有关联数据,最后在内存中完成关联映射。比如查 100 篇文章及其作者,传统方式发 101 条 SQL;用 with('author') 只发 2 条:1 条查文章,1 条用 IN 查所有匹配的作者。
基础用法与常见写法
直接传字符串或数组即可预载入关联:
-
Article::with('author')->select();—— 一对多/一对一都适用 -
Article::with(['author', 'category'])->select();—— 多个关联同时预载入 -
Article::with(['author' => function ($query) { $query->field('id,name,avatar'); }])->select();—— 控制关联表字段,避免冗余数据
嵌套关联与限制场景
支持多层嵌套,但需注意底层实现逻辑:
立即学习“PHP免费学习笔记(深入)”;
-
Article::with('author.profile')->select();—— 先查 author,再用 author.id 集合查 profile - 不支持跨模型深度嵌套(如
author.posts.tags),需拆成两级with(['author' => function() { ... }])或手动 join - 带条件的关联(如只查已审核的评论)建议改用
hasWhere()或闭包 +where(),避免预载入拉取无效数据
配合分页与 count 的注意事项
预载入对分页本身无影响,但要注意:
-
with()不影响count()结果,count()只统计主表行数 - 若需按关联条件分页(如“有图的文章”),不能只靠
with(),得用has()或join() - 大数据量时,避免在
with()闭包中使用limit()或order(),ThinkPHP 不会下推到子查询,可能造成内存中裁剪,效率反而下降
不复杂但容易忽略:预载入不是万能的,它优化的是查询次数,不是单次 SQL 复杂度。关联字段过多或嵌套过深时,仍要考虑是否该用 join 合并查询,或拆分接口按需加载。



















