php - 高效加载或缓存视图,即基于许多嵌套关系
问题描述
我有无限的类别,子类别,子子类别等。
结构:
main
--main1
-----main2 ( child of main1 )
-----main3 ( child of main1 )
--main4
test
我的代码:
在Category.php model
public function categories()
{
return $this->hasMany(Category::class);
}
public function childrenCategories()
{
return $this->hasMany(Category::class);
}
在index.blade.php
:
<select>
@foreach ($categories as $category)
<option value="{{ $category->id }}">{{ $category->category_name }}</option>
@foreach ($cat->childrenCategories as $childCategory)
@include('partials._child_categroy', ['child_category' => $childCategory])
@endforeach
@endforeach
</select>
在 _child_categroy.blade.php 中
<option value="{{ $child_category->id }}">--- {{ $child_category->category_name }}</option>
@if ($child_category->categories)
@foreach ($child_category->categories as $childCategory)
@include('partials._child_categroy', ['child_category' => $childCategory])
@endforeach
@endif
到目前为止一切顺利,但是当我打开调试器时,每个级别的类别都有三个查询,并且有循环!这是我的问题,如果我的链越来越大,那么 foreach 将会增加,这对性能不利!
请问有什么建议吗?
解决方案
这个问题及其潜在问题引起了我的兴趣,因此我想了解更多关于整个问题的信息。我自己创建了一个测试场景。
优化
首先对刀片模板的代码进行一些优化:
// index.blade.php
<select>
@include('categories.options', ['categories' => $categories, 'level' => 0])
</select>
// options.blade.php
@foreach ($categories as $category)
<option value="{{ $category->id }}">{{ str_repeat("--", $level) }} {{ $category->name }}</option>
@include('categories.options', ['categories' => $category->categories, 'level' => $level+1])
@endforeach
然后我生成了一个包含大约 5000 个嵌套类别、8 级深的数据库来测试加载时间。我的假设是,如果您向类别模型添加即时加载,您可以优化加载时间:
// Category.php
// this eager loads ALL nested relations using levels + 1 queries
protected $with = ['categories'];
// this is not needed and doesn't make any difference
protected $with = ['categories.categories'];
结果如下:
Time Queries Memory
--------------------------------------------------
No eager loading 12,81 s 5101 112MB
with eager loading 1,49 s 9 31MB
2 x eager loading 1,54 s 9 31MB
Caching 0,08 s 0 4MB
(stats recorded mainly with debugbar)
因此,正如您所见,急切加载绝对是有意义的。在模型中放置一个也足够了$with = ['categories']
,laravel 将立即加载所有嵌套的 realtionships - 整洁!
缓存
因此,唯一真正的解决方案是让网站尽可能快地加载(我能想到的)是缓存。
// create new job in console
php artisan make:job RenderCategoryView
// RenderCategoryView.php
public function handle() {
// get all top level categories
$categories = \App\Category::where('category_id', null)->get();
$html = \View::make('categories.options', ['categories' => $categories, 'level' => 0])->render();
file_put_contents(resource_path('views/categories/options_cache.blade.php'), $html);
return true;
}
现在你可以像这样替换@include
你的刀片模板:
// index.blade.php
@include('categories.options_cache')
要测试 options_cache 文件的生成,您可以执行以下操作:
php artisan tinker
\App\Jobs\RenderCategoryView::dispatchNow();
在返回索引视图之前,我还删除了现在不必要的数据库查询,新的加载时间为83 ms。并不奇怪,因为现在一切都被缓存了。
要在创建、编辑或删除类别后自动生成新的视图缓存,您应该将其包含在相应的控制器中:
\App\Jobs\RenderCategoryView::dispatch();
在Laravel 文档中阅读有关如何在队列等上调度作业的更多信息。
我的测试代码可以在github上找到。
推荐阅读
- django - 使用数据库中存在的数据呈现 CheckBoxSelectMultiple 表单。[初始值是来自数据库的查询集]
- python - 如何在python中选择然后将相似的csv文件合并到一个文件中?
- ansible - 在 ansible 中获取 Caller Playbook 目录名称
- java - 登录后返回按钮问题,显示确认表单重新提交错误
- python - 如何创建一个案例为间隔的开关案例?
- angular - 带有查询参数的角度路由模块
- perl - 有没有安全运行脚本的方法
- azure-devops - 从哪里获取 Azure Pipeline AppCenterDistribute@3 任务destinationStoreId?
- c# - 来自文件的 C# Stackdriver Trace 凭据
- laravel - laravel 6 调用未定义函数 Facuz\Theme\array_get()