使用 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 * from
rainlab_translate_attributeswhere
locale= 'th' and
model_id= '1275' and
model_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) 如果你正在缓存相关模型,当它们发生变化时也要小心清除缓存。
我有一个名为 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 * from
rainlab_translate_attributeswhere
locale= 'th' and
model_id= '1275' and
model_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 在我的示例中使用而道歉。原文 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) 如果你正在缓存相关模型,当它们发生变化时也要小心清除缓存。