使用maatwebsite/excel导入400MB大Excel文件失败求助
大Excel文件导入内存溢出问题解决
问题背景
需通过Laravel Artisan命令导入400MB的Excel文件,文件包含50万行、300列,存储在public目录下。已尝试调整memory_limit至1500M/2500M,且将导入流程加入队列,但仍出现内存分配失败或进程被自动杀死的问题。
Artisan命令代码
public function handle() { $this->output->title('Excel Import Process Start'); // Check type is valid $file = $this->option('file'); $filePath = public_path('excel-import/' . $file); if (!File::exists($filePath)) { return $this->output->error("File is not found!"); } Log::info("step 1"); try { (new ImportApplications)->withOutput($this->output)->import($filePath); } catch (Exception $e) { $this->output->error("Import Exception"); $this->output->info($e->getMessage()); return false; } $this->output->success('Import successful'); return true; }
导入类代码
class ImportApplications implements ToModel, WithHeadingRow, WithBatchInserts, WithChunkReading, WithUpserts, WithProgressBar , ShouldQueue { use Importable; /** * @param array $row * * @return \Illuminate\Database\Eloquent\Model|null */ public function model(array $row) { Log::info($row); return new Application([ 'FILED_NAME' => $row['column_name'], ... ]); } public function batchSize(): int { return 1000; } public function chunkSize(): int { return 1000; } }
遇到的错误
- 设置
memory_limit: 1500M时,报错:
XMLReader::read(): Memory allocation failed : growing input buffer
- 设置
memory_limit: 2500M时,进程被自动杀死 - 加入队列处理后问题依旧
解决方案
1. 过滤不必要的列,减少每行数据量
添加WithReadFilter接口,仅读取业务需要的列,避免加载300列的全部数据:
use Maatwebsite\Excel\Concerns\WithReadFilter; use Maatwebsite\Excel\Readers\ReadFilter; class ImportApplications implements ToModel, WithHeadingRow, WithBatchInserts, WithChunkReading, WithUpserts, WithProgressBar, ShouldQueue, WithReadFilter { use Importable; // 定义需要导入的列名(根据实际业务调整) private $requiredColumns = ['column_name', 'target_column_1', 'target_column_2']; public function readFilter(): ReadFilter { return new class($this->requiredColumns) implements ReadFilter { private $columns; public function __construct($columns) { $this->columns = $columns; } public function acceptRow($rowIndex) { return true; // 保留所有行,如需跳过特定行可在此处理 } public function acceptColumn($columnIndex, $columnLetter) { // 仅保留需要的列 return in_array($columnLetter, array_flip($this->columns)); } }; } // 其他原有方法保持不变 }
2. 降低Chunk和Batch大小
当前1000行的批次对于300列数据来说内存占用过高,建议降低到200-500行:
public function batchSize(): int { return 200; } public function chunkSize(): int { return 200; }
3. 关闭逐行日志输出
代码中Log::info($row)会将每行数据写入日志,极大消耗内存与IO资源,直接移除该行:
public function model(array $row) { // Log::info($row); // 移除该行日志 return new Application([ 'FILED_NAME' => $row['column_name'], ... ]); }
4. 优化队列进程配置
若使用队列,需单独配置队列进程的内存限制(以Supervisor为例),在配置文件中添加:
php_admin_value[memory_limit] = 1024M
同时延长队列超时时间,避免导入中途超时:
php artisan queue:work --timeout=3600
5. 改用批量插入替代模型实例化
放弃ToModel接口,使用ToCollection手动处理批量插入,减少模型实例化的内存开销:
class ImportApplications implements ToCollection, WithHeadingRow, WithChunkReading, WithProgressBar, ShouldQueue, WithReadFilter { use Importable; private $requiredColumns = ['column_name', 'target_column_1', 'target_column_2']; public function readFilter(): ReadFilter { // 同上述过滤器实现 } public function chunkSize(): int { return 200; } public function collection(Collection $rows) { $insertData = $rows->map(function ($row) { return [ 'FILED_NAME' => $row['column_name'], // 其他字段映射 ]; })->toArray(); // 使用批量插入/更新,避免逐个模型保存 Application::upsert($insertData, ['unique_key'], ['update_column_1', 'update_column_2']); } }
6. 系统层面优化
- 检查服务器空闲内存,避免因内存不足被OOM Killer杀死进程(可通过
dmesg日志确认) - 调整PHP配置:
max_execution_time = 3600、max_input_time = 3600,确保导入有足够执行时间
内容的提问来源于stack exchange,提问作者Harshil Patanvadiya
相关产品推荐
相关产品推荐

