laravel model 读取器的错误用法
缘起
之前一个列表查询很慢,我本地30条记录给我干出60条查询sql来,一个列表而已为什么这么多查询sql?
仔细去看代码也没有发现有foreach 掉sql查询,后来上了laravel-debuger才找到缘由
先说说模型读取器
如果在模型中定义了getxxxAttribute
时,当 Eloquent 尝试获取 xxx
的值时,将会自动调用此访问器,字段的原始值被传递到访问器中,允许你对它进行处理并返回结果,当然,你也可以通过已有的属性,使用访问器返回新的计算值。
以上的这些描述都是对已有的属性,他是对已经查询出来的结果的二次处理,不涉及数据库查询io,只是简单的修改原有值
一些程序员喜欢的写法
很多时候主表存的是附表的id, 比如 主表是user表, 附表为log表, item表,在主表字段可能是这样的 log_id, item_id,但是查询的时候要把相关表记录也查询出来,于是有些程序员喜欢用append这中写法
// Model
class User extends Model
{
// ...
protected $appends = [
'log', 'item', // ...
];
public function getLogAttribute($logId)
{
return Log::where('id', $logId)->first();
}
public function getItemAttribute($itemId)
{
return Item::where('id', $itemId)->first();
}
}
// Controler
use App\Models\User;
class UserController
{
public function list()
{
return User::all();
}
}
乍一看没什么问题,直接查询出来append记录上
append + 读取器 + sql查询危害
如果我们用文档的解释来理解这段代码,当 Eloquent 尝试获取 xxx
的值时,将会自动调用此访问器,字段的原始值被传递到访问器中,如果我有100条记录,每条记录都要访问log, item,那么属性读取器都会被调用,而这个时候在读取器写sql,无疑就是foreach写查询,那么这个列表要查询100 * 2 次sql,这是非常可怕的!!!
解决方案
关联查询
// Model
use App\Models\Log;
use App\Models\Item;
class User extends Model
{
// ...
//protected $appends = [
// 'log', 'item', // ...
// ];
// public function getLogAttribute($logId)
// {
// return Log::where('id', $logId)->first();
// }
// public function getItemAttribute($itemId)
// {
// return Item::where('id', $itemId)->first();
// }
public function log()
{
return $this->hasOne(Log::class);
}
public function item()
{
return $this->hasOne(Item::class);
}
}
// Controler
use App\Models\User;
class UserController
{
public function list()
{
return User::with(['log', 'item']);
}
}
众所周知,模型关联使用的是whereIn 根据id一次可以查询多条记录出来,这才是正解
本作品采用《CC 协议》,转载必须注明作者和本文链接
请问最终改成什么样了?
:see_no_evil:虽然我一开始就是用模型关联+with或者load的方式去处理这种N+1(类似)问题,楼主的出问题的写法倒是从来没有用过。
其实你这个适合使用关联查询。
attribute 适合对现有的字段进行处理,加工后返回。
应该很少有人这样写,关联模型都是用的with