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

3条Eloquent查询优化咨询:当前查询总耗时达1.5秒

查询优化方案

现有代码的核心问题

  • 重复冗余查询:每个统计维度都调用了2次whereHas,额外生成了不必要的EXISTS子查询,和后续withCount的条件完全重复
  • 关联方向不合理:从User表反向关联pictures表统计,本质是对全量用户做遍历判断,数据量大时效率极低
  • 三次独立查询,重复IO开销
  • 大概率缺少必要的数据库索引,导致关联、条件过滤、排序都走全表扫描

第一步:优化SQL逻辑降低查询耗时

方案1:精简现有写法(改动最小)

直接删除冗余的whereHas调用,withCount统计后过滤pictures_count>0即可,同时把重复的时间区间抽成变量避免重复计算:

public function show()
{
    $now = Carbon::now();
    $currentWeekStart = $now->startOfWeek();
    $currentWeekEnd = $now->endOfWeek();
    $lastWeekStart = $currentWeekStart->copy()->subWeek();
    $lastWeekEnd = $currentWeekEnd->copy()->subWeek();

    $currentWeek = User::withCount(['pictures' => fn ($q) => $q->whereBetween('created_at', [$currentWeekStart, $currentWeekEnd])])
        ->where('pictures_count', '>', 0)
        ->orderBy('pictures_count', 'DESC')
        ->limit(10)
        ->get();

    $lastWeek = User::withCount(['pictures' => fn ($q) => $q->whereBetween('created_at', [$lastWeekStart, $lastWeekEnd])])
        ->where('pictures_count', '>', 0)
        ->orderBy('pictures_count', 'DESC')
        ->limit(10)
        ->get();

    $overall = User::withCount('pictures')
        ->where('pictures_count', '>', 0)
        ->orderBy('pictures_count', 'DESC')
        ->limit(10)
        ->get();

    return view('users.leaderboard', compact('currentWeek', 'lastWeek', 'overall'));
}

方案2:调整关联方向(性能提升最明显)

直接从pictures表做分组聚合统计,再关联用户信息,避免遍历全量用户表:

public function show()
{
    $now = Carbon::now();
    $currentWeekStart = $now->startOfWeek();
    $currentWeekEnd = $now->endOfWeek();
    $lastWeekStart = $currentWeekStart->copy()->subWeek();
    $lastWeekEnd = $currentWeekEnd->copy()->subWeek();

    // 本周统计
    $currentWeek = Picture::whereBetween('created_at', [$currentWeekStart, $currentWeekEnd])
        ->selectRaw('user_id, count(*) as pictures_count')
        ->groupBy('user_id')
        ->orderBy('pictures_count', 'desc')
        ->limit(10)
        ->with('user') // 预加载用户信息
        ->get()
        ->pluck('user'); // 提取用户对象,保留pictures_count属性

    // 上周逻辑同上,替换时间条件即可
    $lastWeek = Picture::whereBetween('created_at', [$lastWeekStart, $lastWeekEnd])
        ->selectRaw('user_id, count(*) as pictures_count')
        ->groupBy('user_id')
        ->orderBy('pictures_count', 'desc')
        ->limit(10)
        ->with('user')
        ->get()
        ->pluck('user');

    // 历史累计逻辑
    $overall = Picture::selectRaw('user_id, count(*) as pictures_count')
        ->groupBy('user_id')
        ->orderBy('pictures_count', 'desc')
        ->limit(10)
        ->with('user')
        ->get()
        ->pluck('user');

    return view('users.leaderboard', compact('currentWeek', 'lastWeek', 'overall'));
}

第二步:添加数据库索引

必须给pictures表加两个联合索引,大幅提升过滤、分组速度:

-- 用于时间范围筛选+分组的联合索引
ALTER TABLE pictures ADD INDEX idx_created_at_user_id (created_at, user_id);
-- 用于历史累计分组的索引
ALTER TABLE pictures ADD INDEX idx_user_id (user_id);

第三步:加缓存进一步降低开销

这类排行榜数据不需要实时更新,可根据更新频率设置缓存:

  • 上周排行榜:数据不会再变化,缓存有效期设置为7天,每周一自动刷新
  • 本周排行榜:缓存有效期设置为1~2小时
  • 历史累计排行榜:缓存有效期设置为1天
    用Laravel缓存实现示例:
$currentWeek = Cache::remember('leaderboard.current_week', 3600, function () {
    // 上述本周统计逻辑
});
$lastWeek = Cache::remember('leaderboard.last_week', 86400*7, function () {
    // 上述上周统计逻辑
});
$overall = Cache::remember('leaderboard.overall', 86400, function () {
    // 上述历史累计统计逻辑
});

做完以上三步,查询耗时可以降到100ms以内,加缓存后几乎无数据库开销。

内容的提问来源于stack exchange,提问作者Kylian

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 13:06:03