You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 06:37:46