使用Rainlab Translate填充下拉菜单时查询开销过高问题
解决Rainlab Translate下拉列表N+1查询问题
看起来你遇到了October CMS中Rainlab Translate插件的经典N+1查询困扰——要么下拉选项没翻译,要么翻译正常但性能严重拖慢。我来拆解下问题根源,再给你两个实用的解决方案:
问题根源
Area::lists('name','id')无翻译:lists()是直接在数据库层面执行查询,而Rainlab Translate的翻译逻辑是在模型层面处理的,数据库里存储的name是默认语言的值,自然拿不到当前语言的翻译结果。Area::get()->lists('name','id')触发N+1查询:当你遍历集合访问每个模型的name属性时,TranslatableModel行为会单独去rainlab_translate_attributes表查询该模型当前语言的翻译,100个模型就会产生100次额外查询,直接导致请求耗时飙升。- 缓存集合仍有查询:你之前缓存的是模型转成数组的数据,再hydrate成模型时,模型的翻译缓存(
_translations属性)并没有被保存,所以访问name时还是会触发单独查询。
解决方案
方案1:直接缓存翻译后的键值对(性能最优)
如果你的场景只需要id和翻译后的name,直接缓存最终的键值对是最省资源的方式,全程仅2次查询:
修改Area.php中的方法:
public static function listAreas() { $locale = App::getLocale(); return Cache::rememberForever("areas_list:$locale", function() use ($locale) { // 1. 获取所有Area的基础数据(id和默认语言name) $areas = self::whereNotNull('iso3166_2')->get(['id', 'name']); // 2. 批量查询这些Area对应的当前语言翻译 $translations = \RainLab\Translate\Models\Attribute::where('model_type', self::class) ->where('locale', $locale) ->whereIn('model_id', $areas->pluck('id')) ->get(['model_id', 'value']); // 3. 构建翻译映射表 $translationMap = $translations->pluck('value', 'model_id')->toArray(); // 4. 生成最终的下拉列表数组 $areaList = []; foreach ($areas as $area) { // 有翻译用翻译,无翻译用默认name $areaList[$area->id] = $translationMap[$area->id] ?? $area->name; } return $areaList; }); }
然后在组件中直接使用:
public function areas() { return Area::listAreas(); }
这个方案直接跳过模型的动态属性查询,缓存的就是最终要用的下拉选项数据,完全消除额外查询。
方案2:预加载翻译后缓存模型集合(灵活性更高)
如果你之后可能需要用到Area模型的其他属性,可以用Rainlab Translate提供的批量加载方法,一次性把所有模型的翻译加载完成,再缓存整个集合:
修改Area.php中的方法:
public static function listAreas() { $locale = App::getLocale(); return Cache::rememberForever("areas_collection:$locale", function() use ($locale) { $areas = self::whereNotNull('iso3166_2')->get(); // 批量加载当前语言的翻译到所有模型中 $areas->loadTranslations($locale); return $areas; }); }
组件中使用:
public function areas() { $areas = Area::listAreas(); // 此时访问name属性不会触发额外查询,翻译已预加载 return $areas->pluck('name', 'id'); }
这个方案的核心是loadTranslations()方法,它会一次性查询所有模型的翻译并填充到模型的_translations属性中,缓存集合时会把这个属性一起序列化保存,后续访问name就直接用已加载的翻译了。
选择建议
- 仅需id+翻译name:选方案1,性能最优,资源占用最少。
- 需要模型其他属性:选方案2,保留模型的灵活性,同时解决N+1问题。
内容的提问来源于stack exchange,提问作者Joseph
相关产品推荐
相关产品推荐

