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 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 7
sanders

请问最终改成什么样了?

1个月前 评论
laradocs 1个月前

:see_no_evil:虽然我一开始就是用模型关联+with或者load的方式去处理这种N+1(类似)问题,楼主的出问题的写法倒是从来没有用过。

1个月前 评论

其实你这个适合使用关联查询。

attribute 适合对现有的字段进行处理,加工后返回。

1个月前 评论

应该很少有人这样写,关联模型都是用的with

1个月前 评论
MIYA28118 4周前
晏南风 (楼主) 3周前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!