laravel 大数据分块导出,避免内存溢出
数据来源:服务层(userService.php)
控制器:UserController
public function lists(Request $request)
{
$this->service = new UserService();
$params = $request->all();
$result = $this->service->lists($params);//功能:分页查询数据
if(!empty($params['is_down'])) {
return Excel::download(new SignContractExport($this->service, $params, 100), '合约.xlsx');
}
return $this->successJson($result, "操作成功");
}
导出类:SignContractExport
<?php
namespace App\Exports;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithColumnWidths;
use Maatwebsite\Excel\Concerns\WithStyles;
use Modules\Admin\Services\UserService;
use Maatwebsite\Excel\Concerns\WithTitle;
class SignContractExport implements FromCollection,WithHeadings,ShouldAutoSize,WithStyles,WithTitle,WithColumnWidths
{
protected $batchSize = 100;
public $params = [];
private array $header = [
'ID',
'姓名'
];
public $status;
protected $service;
protected $chunkSize;
/**
* 导出
*/
public function __construct(UserService $service, $params,$chunkSize = 1000)
{
$this->status = $params['status'];
$this->myHeader($params['status']);
$this->service = $service;//业务对象,里面 提供分页查询的方法
$this->params = $params;
$this->chunkSize = $chunkSize;
}
public function map($row): array
{
$itemMap['id'] = $row->id;
$itemMap['mobile'] = $row->user_mobile;
}
public function title():string
{
return '合约导出';
}
public function headings($add = []): array
{
return $this->header;
}
public function collection()
{
$data = collect();
// 使用 getDataChunked 方法
$this->service->getDataChunked($this->params, $this->chunkSize, function ($chunk) use ($data) {
foreach ($chunk as $row) {
$data->push($this->map($row));
}
});
return $data;
}
}
业务层:UserService
/**
* 大数据导出防止内存溢出做分块儿查询
*/
public function getDataChunked($params, $chunkSize, callable $callback)
{
$totalFetched = 0;
// 通过 chunk 的方式来处理数据,每次处理 $chunkSize 条数据
do {
$params['page'] = $totalFetched / $chunkSize + 1;
$params['page_size'] = $chunkSize;
// 获取数据(这里重用 lists 方法)
$result = $this->lists($params);
if (!empty($result['list'])) {//list:列表数据
// 回调函数处理每块数据
$callback($result['list']);
$totalFetched += count($result['list']);
} else {
break; // 如果没有数据,跳出循环
}
} while ($totalFetched < $result['total']);//total:总条数
}
public function lists($params)
{
$list = User::query()->->paginate($params['page_size']??10,$columns = ['*'], $pageName = 'page', $params['page']??null);
return [
'page' => $list->currentPage(),
'page_size' => $list->perPage(),
'total' => $list->total(),
'list' => $data,
];
}
分析与实现
在 Laravel 中处理大数据导出时,直接加载所有数据到内存中可能导致内存溢出问题。为了避免这种情况,你的实现使用了“分块查询”(chunking)的方法,通过分块处理数据,逐块加载和导出,从而有效地管理内存使用。以下是对你的实现如何避免内存溢出的详细分析:
- 数据分块的基本思路
分块查询:通过 getDataChunked 方法,将大数据集分成若干小块(每块 chunkSize 条数据)。每次查询只处理一块数据,处理完后立即释放内存,再进行下一块的处理。这种方式确保了内存中只保留当前处理的那一小部分数据,避免一次性加载所有数据而导致内存溢出。
回调函数的使用:通过传递一个回调函数 callback 来处理每一块查询到的数据块。这样,你可以在导出 Excel 的过程中,逐块地将数据添加到 Excel 文件中,而不是一次性加载所有数据。
- 具体的实现步骤
Step 1: 获取数据总量并初始化分页
首先,在 getDataChunked 方法中通过 lists 方法获取数据的总数 total。
通过初始化的 params[‘page’] 和 params[‘page_size’] 来定义分页的大小和当前页数。
Step 2: 分块查询和处理
使用 do…while 循环进行数据的分块查询。每次循环中,根据当前已获取的数据量计算出当前页数,然后查询这一页的数据。
每次查询到的数据块(如 1000 条)通过 callback 回调函数立即处理(在本例中,导出到 Excel 文件中)。
处理完一块数据后,循环检查是否还需要继续查询更多数据,直到所有数据都被处理完。
Step 3: 内存释放
每次处理完一块数据后,相关的内存就可以被释放,只有当前正在处理的数据块会驻留在内存中。这是通过在回调函数中逐块导出数据而实现的。这意味着即使是非常大的数据集,也能被逐块处理,而不会导致内存溢出。 - Excel 导出流程
在 SignContractExport 类中:
collection() 方法利用 getDataChunked 方法分块查询数据。
每获取一块数据(通过 map() 方法处理格式化后),立即 push 到 data 集合中,直到所有块的数据都被处理完。
最终的 Excel 导出:
在 UserController 中,当 is_down 参数为 true 时,调用 Excel::download,传入 SignContractExport 对象和相关参数。
Laravel Excel 包会利用 collection() 返回的分块数据生成 Excel 文件并下载,过程中每一块数据都是在内存中处理完成后再处理下一块,避免一次性加载所有数据导致内存溢出。
4. 内存优化效果
通过这种分块处理,大数据导出的内存占用相对于一次性加载所有数据来说大大减少。每次只需要处理一块数据,内存中只会驻留当前块的数据,而不需要一次性加载整个数据集。
这种实现方式特别适合在数据量特别大的情况下使用,如数百万行的数据导出。分块查询和处理的方式使得导出过程既高效又能避免内存耗尽的问题。
1万+

被折叠的 条评论
为什么被折叠?



