立即禁用延迟加载来查找 N+1 个问题

发布时间 作者:

Find N+1 problems instantly by disabling lazy loading image

在 Laravel 8 的下一个版本中,您可以完全禁用延迟加载,这将导致异常

在开发过程中阻止延迟加载可以帮助您尽早发现开发过程中的 N+1 错误。Laravel 生态系统有各种工具可以识别 N+1 查询。但是,这种方法通过抛出异常将问题直接呈现在您面前。

演示

让我们通过快速启动框架 8.x 分支的开发版本来快速浏览一下此功能,因为撰写本文时该功能尚未发布。发布后,您将拥有此功能,无需切换到最新的 8.x 分支。

安装

首先,创建一个新应用程序

laravel new strict-lazy-demo

接下来,我们将更新 composer.json 中的 laravel/framework 版本,以确保我们拥有此功能(如果您在下一个版本发布之前尝试它),方法是将版本调整为 8.x-dev

{
"require": {
"laravel/framework": "8.x-dev"
}
}

接下来,运行 composer update 以确保您获得了此分支的最新代码版本

composer update laravel/framework

此时,您应该设置首选数据库。我喜欢使用 Laravel 的默认值运行本地 MySQL 实例,该实例使用 root 用户,没有密码。我发现使用默认的 .env 值在本地快速开始而无需任何配置非常方便。

mysql -uroot -e"create database strict_lazy_demo"

配置好所选数据库后,请确保可以迁移

php artisan migrate:fresh

演示数据

我们将创建一个 Post 模型并从 User 模型定义一个 一对多 关系来演示此功能。我们将从创建 Post 模型以及相关文件开始

# Create a model with migration and factory
php artisan make:model -mf Post

首先,让我们定义 Post 迁移和工厂配置

// Your filename will differ based on when you create the file.
// 2021_05_21_000013_create_posts_table.php
 
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(\App\Models\User::class);
$table->string('title');
$table->longText('body');
$table->timestamps();
});

接下来,根据上面的模式定义 PostFactory 的定义方法

/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'user_id' => \App\Models\User::factory(),
'title' => $this->faker->sentence(),
'body' => implode("\n\n", $this->faker->paragraphs(rand(2,5))),
];
}

最后,打开 DatabaseSeeder 文件,并在 run() 方法中添加以下内容

/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
\App\Models\User::factory()
->has(\App\Models\Post::factory()->count(3))
->create()
;
}

关联模型并阻止延迟加载

现在我们已经创建了迁移、种子和模型,我们已准备好将用户与 Post 模型关联以演示此功能。

User 模型添加以下方法,让用户与 Posts 关联

// app/Models/User.php
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function posts()
{
return $this->hasMany(Post::class);
}

有了它,我们就可以迁移并播种数据库

php artisan migrate:fresh --seed

如果一切顺利,我们应该在控制台中看到以下内容

现在我们可以使用 tinker 检查我们播种的数据和关系

php artisan tinker
 
>>> $user = User::first()
=> App\Models\User {#4091
id: 1,
name: "Nedra Hayes",
email_verified_at: "2021-05-21 00:35:59",
created_at: "2021-05-21 00:35:59",
updated_at: "2021-05-21 00:35:59",
}
>>> $user->posts
=> Illuminate\Database\Eloquent\Collection {#3686
all: [
App\Models\Post {#3369
id: 1,
...

$user->posts 属性实际上调用了数据库,因此是“延迟的”,但没有优化。延迟加载的便利性很好,但从长远来看,它可能会带来沉重的性能负担。

禁用延迟加载

现在我们已经设置了模型,我们可以禁用整个应用程序的延迟加载。您可能只想在非生产环境中禁用它,这很容易实现!打开 AppServiceProvider 类,并在 boot() 方法中添加以下内容

// app/Providers/AppServiceProvider.php
 
public function boot()
{
Model::preventLazyLoading(! app()->isProduction());
}

如果您再次运行 php artisan tinker 会话,这次您应该会收到延迟加载违规的异常

php artisan tinker
 
>>> $user = \App\Models\User::first()
=> App\Models\User {#3685
id: 1,
name: "Nedra Hayes",
email_verified_at: "2021-05-21 00:35:59",
#password: "$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi",
#remember_token: "jHSxFGKOdw",
created_at: "2021-05-21 00:35:59",
updated_at: "2021-05-21 00:35:59",
}
>>> $user->posts
Illuminate\Database\LazyLoadingViolationException with message
'Attempted to lazy load [posts] on model [App\Models\User] but lazy loading is disabled.'

如果您想可视化在视图文件中使用延迟加载时会发生什么,请修改默认路由,如下所示

Route::get('/', function () {
return view('welcome', [
'user' => \App\Models\User::first()
]);
});

接下来,在 welcome.blade.php 文件中的某个位置添加以下内容

<h2>Posts</h2>
@foreach($user->posts as $post)
<h3>{{ $post->title }}</h3>
<p>
{{ $post->body }}
</p>
@endforeach

如果您通过 Valet 或 artisan serve 加载应用程序,您应该会看到类似以下的错误页面

虽然您会在开发过程中收到异常,但只要在服务提供者中正确设置环境检查,意外部署触发延迟加载的代码将继续工作。

了解更多

您可以了解此功能是如何实现的:8.x 添加 eloquent 严格加载模式 - 拉取请求 #37363。非常感谢 Mohamed Said、贡献者,当然还有 Taylor Otwell,他们添加了抛光来有条件地禁用延迟加载。

Paul Redmond photo

Laravel 新闻的作者。全栈 web 开发人员和作家。

Cube

Laravel 新闻稿

加入 40,000 多名其他开发人员,绝不错过任何新的技巧、教程等。

Laravel Forge logo

Laravel Forge

轻松创建和管理您的服务器,并在几秒钟内部署您的 Laravel 应用程序。

Laravel Forge
Tinkerwell logo

Tinkerwell

Laravel 开发人员必备的代码运行器。使用 AI、自动完成和对本地和生产环境的即时反馈来进行调试。

Tinkerwell
No Compromises logo

无妥协

Joel 和 Aaron,来自 No Compromises 播客的两名经验丰富的开发者,现在可以为您的 Laravel 项目聘用。 ⬧ 7500 美元/月固定费率。 ⬧ 无需冗长的销售流程。 ⬧ 无需合同。 ⬧ 100% 返款保证。

无妥协
Kirschbaum logo

Kirschbaum

提供创新和稳定性,确保您的 Web 应用程序取得成功。

Kirschbaum
Shift logo

Shift

正在运行旧的 Laravel 版本?即时自动化的 Laravel 升级和代码现代化,让您的应用程序保持新鲜。

Shift
Bacancy logo

Bacancy

只需 2500 美元/月,即可使用经验丰富的 Laravel 开发人员(拥有 4-6 年经验)为您的项目注入活力。获得 160 小时的专业知识和 15 天的免费试用。立即安排电话会议!

Bacancy
Lucky Media logo

Lucky Media

立即获得 Lucky - Laravel 开发的理想选择,拥有十多年的经验!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar:Laravel 电子商务

Laravel 的电子商务。一个开源软件包,将现代无头电子商务功能的力量带入 Laravel。

Lunar:Laravel 电子商务
LaraJobs logo

LaraJobs

官方 Laravel 工作板

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit:Laravel SaaS 启动套件

SaaSykit 是一个 Laravel SaaS 启动套件,包含运行现代 SaaS 所需的所有功能。支付、漂亮的结账、管理面板、用户仪表板、身份验证、准备好的组件、统计信息、博客、文档等等。

SaaSykit:Laravel SaaS 启动套件
Rector logo

Rector

您无缝 Laravel 升级的合作伙伴,降低成本,加速创新,帮助企业取得成功

Rector
MongoDB logo

MongoDB

通过 MongoDB 和 Laravel 的强大集成来增强您的 PHP 应用程序,使开发人员能够轻松高效地构建应用程序。在使用熟悉的 Eloquent API 的同时,支持事务、搜索、分析和移动用例。了解 MongoDB 的灵活、现代数据库如何改变您的 Laravel 应用程序。

MongoDB
Maska is a Simple Zero-dependency Input Mask Library image

Maska 是一个简单无依赖的输入掩码库

阅读文章
Add Swagger UI to Your Laravel Application image

将 Swagger UI 添加到您的 Laravel 应用程序

阅读文章
Assert the Exact JSON Structure of a Response in Laravel 11.19 image

在 Laravel 11.19 中断言响应的精确 JSON 结构

阅读文章
Build SSH Apps with PHP and Laravel Prompts image

使用 PHP 和 Laravel Prompts 构建 SSH 应用程序

阅读文章
Building fast, fuzzy site search with Laravel and Typesense image

使用 Laravel 和 Typesense 构建快速、模糊的网站搜索

阅读文章
Add Comments to your Laravel Application with the Commenter Package image

使用 Commenter 包为您的 Laravel 应用程序添加评论

阅读文章