解决合并数组/集合内存溢出问题,重构代码并保证订单号唯一
代码重构:优化内存占用并保证order_number唯一性,避免JSONB存储内存溢出
问题描述
如何重构以下代码以优化内存使用,同时确保整个列表中的order_number保持唯一?此外,希望将最终结果保存到JSONB列中,且不会遇到内存不足错误?
原代码
public function generateRandomOrders(int $numbers): array { $faker = \Faker\Factory::create(); $orders = []; for($i=0; $i < $numbers;$i++) { $order = [ 'discount' => $faker->randomFloat(2, 0, 100), 'order_amount' => $faker->randomFloat(2, 10, 1000), 'order_amount_no_vat' => $faker->randomFloat(2, 10, 1000), 'order_name' => $faker->words(3, true), 'order_number' => $faker->unique()->randomNumber(), 'payment_method' => $faker->creditCardType(), 'shipping_cost' => $faker->randomFloat(2, 0, 50), 'status' => $faker->randomElement(['pending', 'processing', 'completed']), 'tag_date' => $faker->date(), 'tag_time' => $faker->time(), 'vat_amount' => $faker->randomFloat(2, 0, 50), 'vat_percent' => $faker->randomFloat(2, 0, 25), ]; $orders[] = $order; } return $orders; } public function test(): JsonResponse { ini_set('memory_limit','32M'); // 故意降低内存限制以测试内存溢出情况 $orders = collect([]); for($i=0; $i < 20;$i++) { $orders = $orders->merge($this->generateRandomOrders(5000)); // 生成5000条随机订单 } $cachedList = CachedList::firstOrNew([ 'company_id' => $this->company_id, 'class' => get_class($this) ]); $cachedList->company_id = $this->company_id; $cachedList->data = $orders->unique('order_number')->values()->toArray(); // 将最终数据保存到jsonb列 $cachedList->class = get_class($this); $cachedList->save(); return response()->json($orders); }
原代码内存问题分析
- 重复数据浪费内存:每次调用
generateRandomOrders都会新建Faker实例,unique()仅在当前批次生效,跨批次可能生成重复order_number,最后需要全量去重,导致内存中同时存储大量重复数据。 - Collection合并开销高:每次
merge都会创建新的Collection对象,10万条数据会产生大量临时对象,内存占用飙升。 - 全量数据加载风险:所有订单先全部加载到内存再去重,32M内存限制下必然溢出。
重构方案
核心优化点
- 共享Faker实例:保证
order_number全局唯一,避免跨批次重复,无需事后去重。 - 增量去重+原生数组:用
order_number作为数组键,生成订单时直接写入关联数组,天然去重,避免存储重复数据。 - 避免全量加载:不维护全量未去重数据,每生成一条就直接进入最终数据集,内存占用可控。
- 移除Collection依赖:原生数组比Collection内存开销更低,操作更高效。
重构后代码
/** * 生成随机订单,直接写入传入的关联数组(按order_number去重) * @param int $numbers 生成数量 * @param \Faker\Generator $faker 共享的Faker实例 * @param array $orders 存储订单的关联数组(键为order_number) */ public function generateRandomOrders(int $numbers, \Faker\Generator $faker, array &$orders): void { for($i=0; $i < $numbers; $i++) { $orderNumber = $faker->unique()->randomNumber(); // 利用数组键唯一性自动去重(即使Faker的unique失效也能兜底) if (!isset($orders[$orderNumber])) { $orders[$orderNumber] = [ 'discount' => $faker->randomFloat(2, 0, 100), 'order_amount' => $faker->randomFloat(2, 10, 1000), 'order_amount_no_vat' => $faker->randomFloat(2, 10, 1000), 'order_name' => $faker->words(3, true), 'order_number' => $orderNumber, 'payment_method' => $faker->creditCardType(), 'shipping_cost' => $faker->randomFloat(2, 0, 50), 'status' => $faker->randomElement(['pending', 'processing', 'completed']), 'tag_date' => $faker->date(), 'tag_time' => $faker->time(), 'vat_amount' => $faker->randomFloat(2, 0, 50), 'vat_percent' => $faker->randomFloat(2, 0, 25), ]; } else { // 极端情况:Faker生成重复(理论上unique()不会出现),重新生成一条 $i--; } } } public function test(): JsonResponse { ini_set('memory_limit','32M'); // 用关联数组存储,键为order_number,自动去重 $orders = []; // 只创建一次Faker实例,保证order_number全局唯一 $faker = \Faker\Factory::create(); for($i=0; $i < 20; $i++) { // 直接写入共享数组,无需返回大数组 $this->generateRandomOrders(5000, $faker, $orders); } $cachedList = CachedList::firstOrNew([ 'company_id' => $this->company_id, 'class' => get_class($this) ]); // 转成索引数组存入JSONB $cachedList->company_id = $this->company_id; $cachedList->data = array_values($orders); $cachedList->class = get_class($this); $cachedList->save(); // 避免返回全量数据导致内存溢出,返回成功状态即可 return response()->json([ 'status' => 'success', 'count' => count($orders) ]); }
重构效果说明
- 内存占用大幅降低:不再存储重复订单,也没有Collection的额外开销,32M限制下可轻松处理10万条数据。
- 绝对唯一的order_number:共享Faker实例的
unique()约束全局生效,再加上数组键的兜底,彻底避免重复。 - 无内存溢出风险:增量式构建数据集,内存始终只存储最终的去重后数据。
- 写入JSONB更高效:直接用
array_values()转成索引数组,无需先处理Collection再转数组。
内容的提问来源于stack exchange,提问作者Marcos DaSilva
相关产品推荐
相关产品推荐

