【laravel】修复所有的 ORM 关联数据读取中都存在的 N+1 问题
什么是 ORM ? 什么是 N+1 问题
ORM
对象关系映射(Object Relational Mapping,简称 ORM )模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。简单的说,ORM 是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。那么,到底如何实现持久化呢?一种简单的方案是采用硬编码方式,为每一种可能的数据库访问操作提供单独的方法。
这种方案存在以下不足:
- 持久化层缺乏弹性。一旦出现业务需求的变更,就必须修改持久化层的接口
- 持久化层同时与域模型与关系数据库模型绑定,不管域模型还是关系数据库模型发生变化,毒药修改持久化曾的相关程序代码,增加了软件的维护难度。
ORM 提供了实现持久化层的另一种模式,它采用映射元数据来描述对象关系的映射,使得 ORM 中间件能在任何一个应用的业务逻辑层和数据库层之间充当桥梁。Java 典型的 ORM 中间件有: Hibernate , ibatis , speedframework 。
ORM 的方法论基于三个核心原则:
- 简单:以最基本的形式建模数据。
- 传达性:数据库结构被任何人都能理解的语言文档化。
- 精确性:基于数据模型创建正确标准化了的结构。
优缺点:简化了操作,降低了性能。
N+1 问题
N+1 一般发生在关联数据的遍历时。在 resources/views/topics/_topic_list.blade.php 模板中,我们对 $topics 进行遍历,为了方便解说,我们将此文件里的代码精简为如下:
@if (count($topics))
<ul class="media-list">
@foreach ($topics as $topic)
.
.
.
{{ $topic->user->name }}
.
.
.
{{ $topic->category->name }}
.
.
.
@endforeach
</ul>
@else
<div class="empty-block">暂无数据 ~_~ </div>
@endif
为了读取 user 和 category,每次的循环都要查一下 users 和 categories 表,在本例子中我们查询了 30 条话题数据,那么最终我需要执行的查询语句就是 30 * 2 + 1 = 61 条语句。如果我第一次查询出来的是 N 条记录,那么最终需要执行的 SQL 语句就是 N+1 次。
如何解决
我们可以通过 Eloquent 提供的 预加载功能 来解决此问题:
public function index()
{
$topics = Topic::with('user', 'category')->paginate(30);
return view('topics.index', compact('topics'));
}
方法 with() 提前加载了我们后面需要用到的关联属性 user 和 category,并做了缓存。后面即使是在遍历数据时使用到这两个关联属性,数据已经被预加载并缓存,因此不会再产生多余的 SQL 查询。
如何确认到问题已解决
安装 Debugbar
使用 Composer 安装:
composer require "barryvdh/laravel-debugbar:~3.2" --dev
生成配置文件,存放位置 config/debugbar.php:
php artisan vendor:publish --provider="Barryvdh\Debugbar\ServiceProvider"
打开 config/debugbar.php,将 enabled 的值设置为:
'enabled' => env('APP_DEBUG', false),
修改完以后, Debugbar 分析器的启动状态将由 .env文件中 APP_DEBUG 值决定。
刷新列表页面即可看到我们的开发者工具栏出现在页面的底部。
转自 5.6. 性能优化 | 《L02 Laravel 教程 - Web 开发实战进阶 ( Laravel 5.7 )》 | Laravel 实战教程