【发布时间】:2022-01-25 20:06:06
【问题描述】:
我们有一个链接到视图的 Livewire 组件,旨在显示一个包含超过 100,000 条记录的数据表,每个记录显示多个(最多 300 条)子记录,来自多个关系(主要是一对多和多对多)。经过长时间的优化和调试工作,为了获得 Eloquent 集合(以 15 个块分页)的繁重查询现在需要大约 15-20 秒才能完成。 该视图还旨在通过激活模式窗口来允许 CRUD 操作。 我们需要解决的主要问题是显示添加或编辑模式窗口的大延迟。此时,我们不需要再次运行数据表查询,因为基础记录尚未更改。主要查询应仅在初始加载时运行,或者在添加或更新记录后运行,此时分页数据表可能已更改。
这是组件的摘录,显示了相关代码:
public function render()
{
//******************** SECTION A ***************
//This is the eloquent collection we use to generate the data-table in the view
//$q is a public property, binded to a input box, for searching purposes. $sortBy and $sortAsc are used to order the data-table.
$fonos = Fono::select('id','cod','titulo','sello_cod','ritmo_cod','propiedad_cod')
->with(['propiedad:id,cod,propiedad','sello:id,cod,sello','ritmo:id,cod,ritmo','artistas:id,cod,artista','autores:id,cod,autor','operadores:id,cod,operador','isrcs:id,isrc']) //Eager loading relationships to reduce queries (only required fields)
->when( $this->q, function($query) {
return $query->where(function( $query) {
$query->where('titulo', 'like', '%'.$this->q . '%')
->orWhere('cod', 'like', '%' . $this->q . '%')
->orWhere('isrc', 'like', '%' . $this->q . '%')
->orWhere('famoso_por', 'like', '%' . $this->q . '%')
->orWhereHas('sello', function ( $query) {
$query->where('sello', 'like', '%' . $this->q . '%');})
->orWhereHas('autores', function ( $query) {
$query->where('cod', 'like', '%' . $this->q . '%');})
->orWhereHas('autores', function ( $query) {
$query->where('autor', 'like', '%' . $this->q . '%');})
->orWhereHas('artistas', function ( $query) {
$query->where('cod', 'like', '%' . $this->q . '%');})
->orWhereHas('artistas', function ( $query) {
$query->where('artista', 'like', '%' . $this->q . '%');})
->orWhereHas('operadores', function ( $query) {
$query->where('cod', 'like', '%' . $this->q . '%');})
->orWhereHas('operadores', function ( $query) {
$query->where('operador', 'like', '%' . $this->q . '%');})
->orWhereHas('isrcs', function ( $query) {
$query->where('isrc', 'like', '%' . $this->q . '%');})
;
});
})
->orderBy($this->sortBy, $this->sortAsc ? 'ASC' : 'DESC')->paginate(15);
//******************** SECTION B ***************
//Here we load other Eloquent models (some with thousands of records), that are used to populate several selects (drop-down lists), that are visible when the modal window is activated to add or edit a record.
$tipos_fonos = TipoFono::select('cod','tipo_fono')->get();
$sellos = Sello::select('cod','sello')->get();
$ritmos = Ritmo::select('cod','ritmo')->get();
$generos = Genero::select('cod','genero')->get();
$idiomas = Idioma::select('id','idioma')->get();
$propiedades = Propiedad::select('cod','propiedad')->get();
$territorios = Territorio::select('cod','territorio')->get();
$clases_artistas = ClaseArtista::select('id','clase_artista')->get();
$artistasLista = Artista::select('cod','artista')->get();
$papelesLista = PapelArtista::select('id','papel_artista')->get();
$autoresLista = AutorFono::select('cod','autor')->get();
$operadoresLista = Operador::select('cod','operador')->get();
$tipos_autores = TipoAutor::select('id','cod','tipo_autor')->get();
//******************** SECTION C ***************
//Finally, we pass the objects to the view
return view('livewire.admin.fonos', [
'fonos' => $fonos,
'tipos_fonos' => $tipos_fonos,
'sellos' => $sellos,
'ritmos' => $ritmos,
'generos' => $generos,
'idiomas' => $idiomas,
'propiedades' => $propiedades,
'territorios' => $territorios,
'clases_artistas' => $clases_artistas,
'artistasLista' => $artistasLista,
'papelesLista' => $papelesLista,
'tipos_autores' => $tipos_autores,
'autoresLista' => $autoresLista,
'operadoresLista' => $operadoresLista,
]);
}
显然,我们需要一种方法来避免不必要的查询,但我们发现了实现该目标的重要限制。例如:
-
我们正在尝试在内存中存储用于在初始加载时填充选择元素的 Eloquent 模型(集合),因为该数据在生命周期中不会更改。因此,我们将这些查询移至 mount 方法,例如:
public function mount() { // This runs once, like a constructor class //Load all models for select drop-downs //all the variables are declared as public properties and also included in the $rules array (i.e. 'lw_generos.*.cod' => 'nullable') $this->lw_tipos_fonos = TipoFono::select('cod','tipo_fono')->get(); $this->lw_sellos = Sello::select('cod','sello')->get(); $this->lw_ritmos = Ritmo::select('cod','ritmo')->get(); $this->lw_generos = Genero::select('cod','genero')->get(); $this->lw_idiomas = Idioma::select('id','idioma')->get(); $this->lw_propiedades = Propiedad::select('cod','propiedad')->get(); $this->lw_territorios = Territorio::select('cod','territorio')->get(); $this->lw_clases_artistas = ClaseArtista::select('id','clase_artista')->get(); $this->lw_artistasLista = Artista::select('cod','artista')->get(); $this->lw_papelesLista = PapelArtista::select('id','papel_artista')->get(); $this->lw_autoresLista = AutorFono::select('cod','autor')->get(); $this->lw_operadoresLista = Operador::select('cod','operador')->get(); $this->lw_tipos_autores = TipoAutor::select('id','cod','tipo_autor')->get(); }所以,为了和这个修改保持一致,我们改变了render方法的C部分:
return view('livewire.admin.fonos', [ 'fonos' => $fonos, 'tipos_fonos' => $this->lw_tipos_fonos, 'sellos' => $this->lw_sellos, 'ritmos' => $this->lw_ritmos, 'generos' => $this->lw_generos, 'idiomas' => $this->lw_idiomas, 'propiedades' => $this->lw_propiedades, 'territorios' => $this->lw_territorios, 'clases_artistas' => $this->lw_clases_artistas, 'artistasLista' => $this->lw_artistasLista, 'papelesLista' => $this->lw_papelesLista, 'autoresLista' => $this->lw_autoresLista, 'operadoresLista' => $this->lw_operadoresLista, 'tipos_autores' => $this->lw_tipos_autores, ]);这种方法的问题在于它会产生错误:
Symfony\Component\ErrorHandler\Error\FatalError 超过 30 秒的最大执行时间 Symfony\Component\ErrorHandler\Error\FatalError::__construct 供应商/laravel/framework/src/Illuminate/Collections/Arr.php:115
注意:我们尝试在视图中直接使用 Eloquent 集合的公共变量,而不显式传递它们,但结果是相同的。我们还考虑过将属性声明为私有或受保护,但数据不会保留(Livewire 文档指出私有或受保护属性无法在组件更新后继续存在)。
-
我们试图避免在视图渲染期间发生的不必要的查询。 $fonos Eloquent 集合应仅在数据更改(以模态形式完成的修改)或使用搜索输入时更新。在其他情况下,集合应该作为对象存储在内存中,准备好传递给视图。但实现该目标存在障碍:首先,雄辩的分页集合目前不能存储为 Livewire 属性(仅允许与 JavaScript 兼容的数据)。其次,即使我们可以将集合分配给公共属性,我们也会得到与前面所述相同的错误。
我们正在使用 Laravel 8.28.1 和 Livewire 2.3.18。
【问题讨论】:
-
您需要在单个查询中改进您的查询或将其放在缓存中。现在您至少要打开 15 个与数据库的连接,这需要很大的时间成本。将 B 部分的大部分结果移动到缓存中。
标签: php laravel laravel-livewire