侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

Laravel框架中预加载、懒加载、with()、load()的概念及使用场景解析

Laravel  /  管理员 发布于 3年前   3382

Laravel Eloquent中的懒加载和预加载以及它如何在后台运行?

Eloquent 模型关系

Classes 班级表 与 Student 学生表,是一对多关系。

// Classes.php
class Classes extends Model
{
    public function student()
    {
        return $this->hasMany(Student::class);
    }
}
// Student.php
class Student extends Model
{
    public function Classes()
    {  
        return $this->belongsTo(Classes::class);
    }
}

当我们在控制器中请求时:

public function index()
{
    $Classes = Classes::query()->find(1);
} 

此时执行的 SQL 语句只有一条

select * from `classes` where `classes`.`id` = '1'

当我们要访问 Classes 1 的学生时:

public function index()
{
    $Classes = Classes::query()->find(1);
    $Classes->student;
} 

此时执行的 SQL 语句是

select * from `classes` where `classes`.`id` = '1'
select * from `student` where `student`.`classes_id` = '1' 

产生了一次额外查询。

因为 Eloquent 仅处理了对 Classes 模型进行了查询,并不知道你想要 student 的关联数据,所以并没有为你准备好它,这样可以避免不必要的查询,来加快返回效率。


Laravel 中对所有模型关联关系的访问,如果没有使用 with() 提前告诉 Eloquent 你想要关联的关系,从而进行访问时,就叫 懒加载。通常也是 N+1 问题经常会出现的地方。


假如我们要访问所有班级的学生呢?

public function index()
{
    $Classes = Classes::query()->get(); // 班级表有 10 条记录
    foreach($Classes as $Classes)
    { 
        $Classes->student;
    }
} 

这时就会产生 N+1 的的问题,看一下 SQL 语句

select * from `student` where `student`.`classes_id` = '1'
select * from `student` where `student`.`classes_id` = '2'
...
select * from `student` where `student`.`classes_id` = '9'
select * from `student` where `student`.`classes_id` = '10

产生了 10 次 SQL 查询,加上本身对所有班级的查询,一共 11 次,这就是 N+1 了。


我为什么用 Classes 班级表 和 student 学生表来举例呢?

因为我要用更直白的话语来描述。


学校晚上 5 点下班,学生们都回宿舍休息了。

晚上 10 点时,班长突然接到上头指示,来了个急活,需要学生来大扫除。

因为学生并不知道晚上要大扫除,所以都脱衣服上床睡觉了,

这个时候班长是不是要把他们挨个都叫起来呀?

学生从被窝起来,打着哈欠,一边穿衣服一边嘴里骂骂咧咧,然后回到教室大扫除,这个过程就是 `懒加载`。

其实 懒加载 并没有什么坏处,它在执行效率上是最优的。

程序只查询预期中的的数据,并不知道你要访问它的模型关系,当你需要访问模型关系时,再去查询一次就好了。


但是我们需要注意的是,当查询结果不是一个单条记录 (Model),而是多条记录 (Collection) 时,如果这个时候要去访问 Collection 中每条记录的模型关系,那就需要使用接下来的 预加载 了。

否则就会产生上文的 N+1 的问题。


还是刚才的查询,这次使用 with() 预加载学生关系。

public function index()
{
    $Classes = Classes::query()->with('student')->get(); // 班级表有 10 条记录
    foreach($Classes as $Classes)
    {
        $Classes->student;
    }
}

此时 SQL 查询就只有 2 条。

select * from `classes`
select * from `student` where `student`.`classes_id` in (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

还是上面的班级例子,再举一次:


班长接到一个新任务,是个急活,班长周一就通知下去本周六大扫除,所有学生下课后必须守候在教室等着大扫除,这就叫`预加载`。

学生信息已经准备好了,只等着你去访问它就可以了。


由此可以得出结论:

当你的查询结果返回的是一个 单条记录 (Model),此时 懒加载 和 预加载 其实没有区别,因为每一个模型关系就是一次查询,所以这里不论是使用 with() 预加载,还是直接使用 懒加载,对于单条记录的模型来说,最终 SQL 执行条数都是一样的。

但当你的查询结果返回的是多条记录 (Collection) 时,如果要访问模型关系,就必须使用 with() 预加载,否则就会产生 N+1 问题。


那么 load() 是干嘛的?我们这样查询可以吗?

public function index()
{
    $Classes = Classes::query()->load('student')->find(1);
}

结果是肯定不行的,会直接报以下错误

BadMethodCallException: Call to undefined method Illuminate\Database\Eloquent\Builder::load()

在一个查询没有使用 get() 或 find() 返回之前,它都是一个 EloquentBuilder 对象,我们的所有 where()、with()、whereIn()、等方法都是在构造查询语句,但其实并没有数据被真正的查询。


当这条语句被执行时,并没有 SQL 语句被执行。

Classes::query()->with('student');

load() 是模型 Model 才能使用的方法, EloquentBuilder 是不能使用的。


看一下如何使用 load()

public function index()
{
    $Classes = Classes::query()->find(1);
    $Classes->load('student'); 
}

此时被执行的 SQL 语句是:

select * from `classes` where `classes`.`id` = '1' limit 1
select * from `student` where `student`.`classes_id` in (1)

其实上面的查询和下面的这句 with() 是一模一样的:

public function index()
{
    $Classes = Classes::query()->with('student')->find(1); 
}

那么 load() 的使用场景是?

假如我们使用依赖注入的方式来查询 Factory,但同时我们还要把 student 关联一并返回的时候,就会用到它了。

public function index(Classes $Classes)
{ 
    $Classes->load('student');
}

with() 是查询时一并加载模型关联,load() 是先有模型被查询后,再加载模型的关联时使用的。

希望本篇文章能帮你理清这些概念,如果我有哪里表达不清楚的,还请指正。


  • 上一条:
    Saga分布式事务
    下一条:
    Visit是一个漂亮的人机交互的HTTP CLI 工具
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • Laravel 11.15版本发布 - Eloquent Builder中添加的泛型(0个评论)
    • Laravel 11.14版本发布 - 新的字符串助手和ServeCommand改进(0个评论)
    • Laravel 11.12版本发布 - Artisan的`make`命令自动剪切`.php `扩展(0个评论)
    • Laravel的轻量型购物车扩展包:binafy/laravel-cart(0个评论)
    • Laravel 11.11版本发布 - 查看模型中的第三方关系:show(0个评论)
    • 近期文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-07
    • 2017-08
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-09
    • 2020-10
    • 2020-11
    • 2021-01
    • 2021-02
    • 2021-03
    • 2021-04
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-04
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    • 2024-05
    • 2024-06
    • 2024-07
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客