如何优化Laravel商品折扣接口 解决商品列表查询响应过慢问题
性能瓶颈核心原因
原代码存在典型的N+1查询问题,是高耗时的核心诱因:
- 遍历全量商品时,每轮循环都会发起2次独立数据库查询:1次查询用户信息、1次查询当前商品分组对应用户的折扣规则
- 按366件商品计算,接口总共会执行
1(商品列表查询) + 366*2 = 733次数据库IO请求,数据库连接、查询的开销叠加直接把响应时间拉到秒级 - 次要损耗:循环内重复执行路径生成逻辑,无差别返回表全量字段带来额外序列化开销
可直接上线的优化代码
优化核心思路:把循环内的重复查询全部提到循环外一次性执行,所有折扣匹配、价格计算全部在内存中完成,把总数据库查询次数从700+降到3次以内。
public function apiindex($userid) { // 仅查询1次用户信息,移出循环逻辑 $user = User::where('bayikodu', $userid)->first(); // 提前处理用户不存在的边界场景,避免后续逻辑报错 if (!$user) { return response()->json([], 403); } // 一次性拉取该用户所有分组的折扣规则,按键值对存储方便后续匹配,仅1次查询 $iskontoMap = $user->iskontolar()->pluck('iskonto', 'grup_kodu')->toArray(); // 查询商品时按需选取列表所需字段,避免返回无用字段增加传输、序列化开销 $urunler = Stok::where('StokMiktari', '>', 0) ->get(['SiraNo', 'SatisFiyati', 'file_path', /* 此处补充商品列表实际需要的其他字段 */]); // 内存遍历计算,无额外数据库查询 $urunler->transform(function (Stok $stok) use ($iskontoMap) { $fiyati = $stok->SatisFiyati; // 直接从预加载的折扣映射表取对应费率,无匹配则默认0 $iskonto_orani = $iskontoMap[$stok->SiraNo] ?? 0; $iskontolu_fiyat = $fiyati - ($fiyati * $iskonto_orani) / 100; $stok['SatisFiyati'] = ceil($iskontolu_fiyat); $stok['file_path'] = asset($stok->file_path); return $stok; }); return response()->json($urunler); }
优化后接口响应耗时通常可以降到100ms以内。
可选进阶优化
- 索引优化:给
Stok表的StokMiktari字段加普通索引,给User表的bayikodu字段加唯一索引,给用户折扣关联表的user_id、grup_kodu字段加联合索引,进一步降低单条SQL的执行耗时 - 缓存配置:如果商品数据、用户折扣规则更新频率低,可以给接口加10-60秒的请求缓存,相同用户重复请求直接返回缓存结果,完全跳过查库计算逻辑
- 字段裁剪:严格控制接口返回字段,不要返回商品详情、内部备注等列表渲染用不到的字段,减少JSON序列化和网络传输开销
- 路径预处理:如果静态资源域名固定,可以直接在商品表存储完整访问路径,或者在模型层做好访问器自动拼接路径,减少接口层重复计算
内容的提问来源于stack exchange,提问作者Merih Dursun
相关产品推荐
相关产品推荐

