使用 Rainlab Translate 查询下拉菜单的高查询开销

High query overhead with Rainlab Translate for a dropdown menu

我有一个名为 Area 的模型,其中包含我需要填充下拉列表的区域名称列表。该列表是使用 Rainlab 翻译插件翻译的。

如果我只是直接 Area::lists() 那么列表就不会被翻译。但是,如果我执行 Area::get()->lists() 然后它被翻译但是一个查询是 运行 在 rainlab_translate_attributes table 上对于下拉列表中的每个项目,导致约 100 个查询 运行 和 1.5 秒的请求持续时间。

型号

<?php namespace Namespace\PluginName\Models;

use Model;

class Area extends Model
{
    public $implement = ['RainLab.Translate.Behaviors.TranslatableModel'];

    public $translatable = ['name'];

    // .... 
}

查看

<div class="form-group {{ errors.first('location_id') ? 'has-error' }}">
    {{ form_label('area_id','Area') }}
    {{ form_select('area_id', {'': 'Select...'} + area, null, {'class': 'form-control', 'placeholder': 'Select...'}) }}
    <small class="text-danger" data-validate-for="area_id"></small>
</div>

组件选项 #1(快速查询但项目未翻译)

public function areas() {
    return Area::lists('name','id');
}

组件选项 #2(项目已翻译,但查询约 100 次且非常慢)

public function areas() {
    return Area::get()->lists('name','id');
}

在其他类似情况下,我会添加 public $with = ['relation'],但 rainlab_translate_attributes table 似乎没有我可以将 Area 模型关联到的模型。

更新

我在我的 Area.php 模型中创建了以下函数:

public static function listAreas()
{
    $areas = Cache::rememberForever("all:" . App::getLocale()  , function() {
        return self::
        whereNotNull('iso3166_2')
        ->get()
        ->toArray();
    });

    return  self::makeCollection( $areas ) ;
}

public static function makeCollection ( array $models = [] )
{
    return self::hydrate( $models );
}

...然后在我的组件中,我尝试了:

$areas = Area::listAreas(); <-- 这会立即读取缓存数据

$areas->lists('name','id'); <-- 这会导致为集合中的每个项目生成一个新查询,这是一个查询的示例:

select * fromrainlab_translate_attributeswherelocale= 'th' andmodel_id= '1275' andmodel_type= 'Namespace\PluginName\Models\Area' limit 1

我已验证 App::getLocale() 已正确设置为 th

你需要JOIN手动我想,似乎有no functionality available for collection.

$locale = 'de';
$query = \HardikSatasiya\DemoTest\Models\Relation::query();

$query->select($query->getModel()->getTable() .'.*');
$query->addSelect('rainlab_translate_attributes.attribute_data');
$query->leftJoin('rainlab_translate_attributes', function($join) use ($locale, $query) {
    $join
        ->on(\Db::raw(\DbDongle::cast($query->getModel()->getQualifiedKeyName(), 'TEXT')), '=', 'rainlab_translate_attributes.model_id')
        ->where('rainlab_translate_attributes.model_type', '=', get_class($query->getModel()))
        ->where('rainlab_translate_attributes.locale', '=', $locale)
    ;
});

$data = $query->get();
$translatedArray = [];
foreach ($data as $value) {
    if(is_null($value->attribute_data)) {
        $translatedArray[$value->id] = $value->name;
    }
    else {
        $translations = json_decode($value->attribute_data);
        $translatedArray[$value->id] = $translations->name;
    }

}
dd($translatedArray);

也许这会对你有所帮助。

另一个可能影响速度的想法(如果不是查询数量的话)- 索引模型的名称 属性:

public $translatable = [
    ['name', 'index' => true]
];

参考:https://github.com/rainlab/translate-plugin#indexed-attributes

我有相同的要求并使用 caching 解决了它。如果您不想缓存查询,请忽略此答案,但我认为您应该考虑一下。

1) 确保您的 RainLab Translator 已配置,因此当使用 App::getLocale() returns 时,翻译器的活动区域设置不是 Laravel 的。

2) 在您的模型中创建一个方法以供 front-end 使用。目的是缓存模型/关系和翻译属性。

例如AreaModel.php

public static function listAreas()
{
       $areas = Cache::tags([  'areas' ])
            ->rememberForever(  "all:" . App::getLocale()  , function() {
                return self::
                    with(['relation_model_name']) // Fetch the Relation
                    ->get()
                    ->toArray();
            });

    return  self::makeCollection( $areas ) ;
}

public static function makeCollection ( array $models = [] )
{
    return self::hydrate( $models );
}

a) 这里我们使用包含活动语言环境的键对查询进行缓存

b) 我们正在为相关模型添加 with

c) 我们只是缓存整个集合(没有 pluck / lists )并转换回 eloquent 模型实例。

优点是现在在您的组件中 Area::listAreas(); 将 return 缓存的集合,您可以像其他操作一样操作。

$areas = Area::listAreas(); // collection ( Area + Relation )

$dropdown = $areas->pluck('name', 'id'); // get Dropdown values for Areas...

一些考虑是在每次更新、添加或删除记录(模型 + 关系)时清除缓存(删除缓存标记/键)。

下面是 Store Model 及其关系模型 Business Type;

的 Redis 缓存存储的屏幕截图

更新 :

首先,我为假设每个人都使用 Redis 在我的示例中使用而道歉。原文 post 应该更侧重于实现。请不要像我一样复制/粘贴代码。

a) 在我最初的回答中,我使用 hydrate() 方法 post 编辑了代码,以从缓存的记录中创建一个现有的模型实例。这令人困惑且不必要,但我怀疑它与 rainlab 翻译的相关查询有关。 (需要确认)

b) return self::whereNotNull('iso3166_2')->get()->lists('name','id') 足以缓存区域记录。

c) 在我的评论中,我使用了 pluck,因为 lists 已被弃用。 pluck return 合集 - 请参阅 here and

$areas = self::whereNotNull('iso3166_2')->pluck('name', 'id') ; // collection
$areas->toArray();

我还没有在 October 中尝试过 file-based 缓存,不确定它的行为与 Redis 的对比。

同样,一些注意事项;

a) 请将您的缓存键命名为独特且有意义的名称,在我的 post all + locale 中与缓存标签 areas 相关。例如areas.iso3166_2.locale(避免覆盖)

b) 添加 Cache::forget('key');在你的模型中 afterSave & afterDelete 方法

c) 如果你正在缓存相关模型,当它们发生变化时也要小心清除缓存。