Laravel查询构建器查询耗时优化求助:20秒获取2500条数据
Laravel 查询性能优化方案
针对你的查询耗时问题,核心优化方向是解决orOn关联导致的结果集膨胀、添加索引减少数据库扫描范围,以及优化关联逻辑避免不必要的重复数据处理,具体方案如下:
1. 重构feedback表关联逻辑,消除orOn的性能损耗
原查询中orOn会让数据库对每个customer匹配所有符合三个ID条件的feedback记录,导致结果集急剧膨胀,后续groupBy又要额外消耗资源去重。可以用以下两种方式替代:
方案A:用IN条件替代orOn
DB::table('customer as c') ->leftJoin('feedback as fr', function($leftJoin) { $leftJoin->whereRaw('fr.id IN (c.active_feedback_req_id_1, c.active_feedback_req_id_2, c.active_feedback_req_id_3)'); }) // 后续关联、条件、排序保持不变
方案B:子查询预取feedback数据(更高效)
如果每个customer最多对应一条feedback记录,直接用子查询获取对应数据,避免join带来的结果集冗余:
DB::table('customer as c') ->select([ 'c.id as customer_id', 'c.name as customer_name', 'c.created_at as customer_creation_date', // 其他customer字段... // 子查询获取feedback聚合数据 DB::raw('( SELECT CONCAT(service_taken, "|", service_provider, "|", reminders_sent) FROM feedback WHERE id IN (c.active_feedback_req_id_1, c.active_feedback_req_id_2, c.active_feedback_req_id_3) LIMIT 1 ) AS feedback_info'), // 其他字段... ]) ->leftJoin('feedback_request_status as frs', function($join) { $join->on('fr.id', '=', 'frs.id'); }) ->where('c.business_location_id', $request->location_id) ->where('c.is_active', 1) ->orderBy('c.created_at','DESC') ->get();
后续可以在PHP代码中拆分feedback_info字段,提取对应的值。
2. 添加关键索引,减少数据库扫描范围
索引是提升查询速度的核心,针对你的查询创建以下索引:
- customer表:覆盖查询过滤和排序条件
CREATE INDEX idx_customer_location_active_created ON customer(business_location_id, is_active, created_at); CREATE INDEX idx_customer_feedback_ids ON customer(active_feedback_req_id_1, active_feedback_req_id_2, active_feedback_req_id_3); - customer_service表:加速关联查询,避免全表扫描
CREATE INDEX idx_customer_service_customer ON customer_service(customer_id); -- 如果需要频繁获取service_taken和service_provider,添加覆盖索引 CREATE INDEX idx_customer_service_customer_fields ON customer_service(customer_id, service_taken, service_provider); - feedback表:加速状态关联
CREATE INDEX idx_feedback_status ON feedback(feedback_request_status_id);
3. 优化customer_service关联,避免结果集重复
原查询leftJoin customer_service会导致一个customer对应多条服务记录时,结果集重复,groupBy被迫去重消耗资源。改为只取每个customer的最新服务记录:
DB::table('customer as c') ->leftJoin(DB::raw('( SELECT cs.* FROM customer_service cs INNER JOIN ( SELECT customer_id, MAX(created_at) as latest_time FROM customer_service GROUP BY customer_id ) cs_latest ON cs.customer_id = cs_latest.customer_id AND cs.created_at = cs_latest.latest_time ) as cs'), 'c.id', '=', 'cs.customer_id') // 其他关联和条件保持不变
4. 修正GROUP BY与SELECT的逻辑一致性
原查询GROUP BY c.id时,SELECT包含了非customer表的非聚合字段(如fr.service_taken),在MySQL严格模式下会报错,且数据库会随机选取值。如果一个customer对应多条关联记录,需明确聚合方式:
->select([ // customer字段... DB::raw('MAX(fr.service_taken) as service_taken'), DB::raw('MAX(fr.service_provider) as service_provider'), DB::raw('MAX(cs.service_taken) as customer_service_taken'), // 其他非customer字段同理 ])
5. 可选:分页查询降低单次负载
如果业务允许,不要一次性获取2500条数据,改用分页:
->orderBy('c.created_at','DESC') ->paginate(50); // 每页50条,耗时会大幅降低
内容的提问来源于stack exchange,提问作者HarshGaikwad
相关产品推荐
相关产品推荐

