ThinkPHP6 模型关联预加载:我因为 `with` 里写错了一个方法名,把 N+1 查成了 N×10+1
昨天给后台订单列表做优化,明明加了 `with('user')`,打开调试工具一看,SQL 还是刷了几十条。盯着屏幕愣了三分钟,才发现自己踩了一个特别蠢的坑——`with` 里写的方法名和模型里定义的关联方法名对不上。
先贴错误代码,估计有人跟我一样栽过:
```php
// Order 模型里我定义的关联
public function buyer()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
// 控制器里我写的
$list = Order::with(['user'])->select();
```
看出来了吗?模型里叫 `buyer`,`with` 里我写成了 `user`。ThinkPHP6 不会报错,也不会抛异常,它很"贴心"地把这个当成普通属性去处理了。结果就是:主查询走了一条,然后循环里每次访问 `$item->user` 都触发一次新查询。我 30 条分页数据,硬生生刷了 31 条 SQL,比 N+1 还离谱。
正确的写法其实就一行的事:
```php
// 方法名必须和模型里定义的完全一致
$list = Order::with(['buyer'])->select();
// 或者你要同时加载多个,也得一个个核对清楚
$list = Order::with(['buyer', 'items', 'address'])->select();
```
这里有个细节:如果你 `with` 里写了个完全不存在的关联名,比如 `with(['abc'])`,TP6 也不会报错,而是静默忽略。这个设计我觉得有点坑新手——要是能像 Laravel 那样严格校验关联名存在性就好了。
另外说个我后来养成的习惯:关联方法名尽量和数据库外键字段名保持一致。比如 `user_id` 对应 `user()`,`parent_id` 对应 `parent()`,这样 `with` 的时候不容易脑抽写错。之前有个项目里我用 `author` 做方法名但外键是 `user_id`,结果三个月后自己回来维护,条件反射写了 `with(['user'])`,又踩一次。
还有个进阶场景:关联统计 `withCount` 和方法名不一致的时候,返回的统计属性名也会跟着变。比如 `withCount('buyer')` 会生成 `buyer_count`,如果你方法名叫 `user` 但 `withCount(['buyer'])`,那属性里根本找不到这个统计值,排查起来更头大。
最后分享个我现在的自查动作:写完 `with` 之后,强制打开 `Db::listen` 或者看调试面板的 SQL 日志,数一下查询条数是不是 1+N 或者就是 1。如果数字对不上,第一时间检查方法名拼写,比后期压测才发现性能问题强一百倍。
你们有没有被这种"静默失败"坑过?我反正已经长记性了,现在写关联加载必须开着 SQL 监控才敢保存文件。

