ThinkPHP关联预加载核心是解决N+1查询,用with()实现主表+关联表IN或JOIN查询;withCount()单独统计数量;闭包条件仅作用于关联模型;跨表排序须用join()或PHP层处理。

ThinkPHP 关联模型预加载核心是解决 N+1 查询问题,让原本查 10 条用户再逐条查 profile 的 11 次 SQL,压缩成 2 次(主表 + 关联表 IN 查询)甚至 1 次(用 JOIN)。关键不在“怎么写”,而在“什么时候用什么方式、避免哪些典型错误”。
with() 是最常用预加载,但只管“查关联数据”
它不统计数量、不排序主列表、不跨表筛选主表记录。基本写法:
✅ 正确:User::with('profile')->select()
❌ 错误:User::with('profile')->where('profile.status', 1)->select() —— 这里 profile.status 不在主表,条件无效。
多个关联用逗号或数组:User::with('profile,posts')->select() 或 User::with(['profile', 'posts'])->select()
嵌套关联也支持:User::with('profile.avatar')->select()(前提是 profile() 和 avatar() 方法都已正确定义)
要统计数量?必须 withCount() 单独调用
with() 和 withCount() 完全独立,不能混写:
✅ 合法:User::with('posts')->withCount('posts')->select()
❌ 无效:User::with(['posts', 'posts_count']) 或 User::with('posts.count')
统计字段名固定为「关联方法名」+「_count」小写下划线格式:profile() → $user->profile_countuserProfile() → $user->user_profile_count
多个统计传数组:withCount(['posts', 'comments']) → 得到 $user->posts_count 和 $user->comments_count
需要带条件查关联?闭包必须写在 with() 里
闭包只作用于关联模型本身,和主查询隔离:User::with(['posts' => function ($q) { $q->where('status', 1)->limit(3); }])->select()
注意三点:
• 字段裁剪时,必须包含外键和主键(如 $q->field('id, user_id, title')),否则关联数据绑定失败
• 闭包中 order() 只影响该关联集合内部顺序,不影响主列表排序
• 不能引用主表字段,比如 $q->where('user_id', $this->id) 会报错
想按关联字段排序主列表?不能靠 with()
with() 预加载无法实现跨表排序。两种可行方案:
• 用 join():显式联表,把排序字段纳入主查询范围UserModel::alias('u')->join('user_profile p', 'u.id = p.user_id', 'LEFT')->order('p.nickname asc')->select()
• PHP 层排序(仅适合小数据量):$users->collection()->sortBy('profile.nickname'),前提是 profile 已预加载且字段存在
别在主查询写 order('profile.nickname'),TP 不识别,会报 Unknown column



















