
本文介绍在 laravel 中使用 laravel-excel 导入含 1600+ 行数据的 excel 文件时,规避“最大执行时间超限”和“内存耗尽”错误的实战方案,涵盖分块读取、批量插入/更新(upsert)、内存优化及服务端配置建议。
本文介绍在 laravel 中使用 laravel-excel 导入含 1600+ 行数据的 excel 文件时,规避“最大执行时间超限”和“内存耗尽”错误的实战方案,涵盖分块读取、批量插入/更新(upsert)、内存优化及服务端配置建议。
在处理 Excel 导入与数据库同步任务时,看似仅 1600 条记录的文件却频繁触发 max_execution_time exceeded 或 Allowed memory size exhausted 错误,根本原因在于原始实现中:
- 每行调用
Model::find()+ 单条save()→ 触发 N+1 查询与 ORM 开销; - 未启用分块(chunking),导致全部数据一次性加载至内存;
- 使用
create()逐行写入临时表,缺乏批量操作支持。
✅ 推荐解决方案:分块读取 + 批量 upsert 同步
Laravel-Excel v3.1+ 原生支持 WithChunkReading 和 WithUpserts,可显著降低内存占用并提升性能。以下是优化后的完整实现:
// app/Imports/BookPriceImport.php
namespace App\Imports;
use App\Models\Book;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\ToCollection;
use Maatwebsite\Excel\Concerns\WithChunkReading;
use Maatwebsite\Excel\Concerns\WithUpserts;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
class BookPriceImport implements ToCollection, WithChunkReading, WithUpserts, WithHeadingRow
{
public function chunkSize(): int
{
return 100; // 每次处理 100 行,平衡内存与效率
}
public function uniqueBy(): array
{
return ['book_id']; // 假设 Excel 中首列为 'book_id',对应数据库主键或唯一索引字段
}
public function collection(Collection $rows)
{
$data = $rows->map(function ($row) {
return [
'book_id' => $row['book_id'] ?? null,
'paperbook_price' => $row['paperbook_price'] ?? null,
'ebook_price' => $row['ebook_price'] ?? null,
'audio_price' => $row['audio_price'] ?? null,
];
})->filter()->values()->all();
if (!empty($data)) {
// 使用 upsert:匹配 book_id 存在则更新,否则忽略(不插入新书)
Book::upsert(
$data,
['book_id'], // 匹配条件(必须为数据库中带 UNIQUE 索引的字段)
['paperbook_price', 'ebook_price', 'audio_price'] // 需更新的字段
);
}
}
}? 关键前提与注意事项:
- 数据库表
books的book_id字段必须添加唯一索引(如ALTER TABLE books ADD UNIQUE INDEX idx_book_id (book_id);),否则upsert将失效或报错; - 若 Excel 列名非标准(如
book_id,paperbook_price),请配合WithHeadingRow使用真实表头,或改用WithCustomCsvSettings映射; -
upsert()是 Laravel 8.0+ 原生方法,若使用低版本,请升级 Laravel 或改用insertOnDuplicateKeyUpdate()(需 MySQL)或分批updateOrCreate(); - 避免在
collection()中执行额外查询(如Book::find()),所有逻辑应基于内存内数据完成。
? 服务端兜底配置(仅当必要时调整):
若业务规模持续增长(如单次导入超 10,000 行),可在 php.ini 中适度放宽限制(生产环境慎用):
max_execution_time = 120 memory_limit = 256M
同时建议通过 Artisan 命令行方式执行导入(避开 Web 请求超时):
php artisan import:books path/to/books.xlsx
? 总结:
1600 行并非大数据量,问题根源在于低效的数据处理模式。通过 WithChunkReading 控制内存峰值、upsert 替代逐行查改、配合数据库唯一索引保障原子性,即可在默认资源限制下稳定完成导入与差异更新。后续扩展时,还可结合队列(ShouldQueue)实现异步处理,彻底解耦请求响应与耗时任务。


















