laravel 处理大文件应流式读取、分块处理并结合队列:csv 用 fopen + fgetcsv,文本用 fgets/stream_get_line,复杂逻辑交由队列异步执行,辅以内存优化与断点续传。

在 Laravel 命令行(Artisan 命令)中处理大文件(如 CSV、日志、JSONL 等)时,核心原则是:**避免一次性加载整个文件到内存**,否则容易触发内存溢出(Allowed memory size exhausted)。推荐采用流式读取 + 分块处理 + 适当使用队列的方式。
使用 fopen + fgetcsv 流式读取 CSV
适用于百万行级 CSV 文件。不依赖第三方包,内存占用稳定在几 MB 内。
- 用
fopen('php://stdin', 'r')或fopen($path, 'r')打开文件句柄,不加载全文 - 循环调用
fgetcsv()逐行解析,每行返回数组,自动处理引号、转义等 - 每处理 N 行(如 1000 行)执行一次数据库批量插入(
DB::table()->insert()),并手动触发gc_collect_cycles()辅助释放 - 避免在循环中新建 Eloquent 模型实例(如
new User()),改用数组 +insert()更高效
对超大文本/日志文件使用 stream_get_line 或 fgets
适合按行解析的纯文本(如 access.log、自定义分隔日志)。
-
fgets($fp)每次读取一行(含换行符),内存占用与最长单行长度相关,非整文件大小 -
stream_get_line($fp, $length, $ending)可按指定分隔符(如"\n")截取,更灵活 - 配合
pcntl_fork()(仅 CLI)可多进程分片读取,但需谨慎处理共享资源和信号 - 建议加进度提示:
info("已处理 {$lineNum} 行...");,用$lineNum % 10000 === 0控制输出频率
结合 Laravel 队列异步处理分块数据
当单行解析逻辑复杂(如调用 API、图像处理、关联查询),或需保障可靠性时,将“解析”与“执行”分离。
- 主命令只负责切分文件:按行数(如每 5000 行)生成临时分片文件,或直接分段读取后 dispatch 到队列任务
- 队列任务接收起始行号、行数、文件路径,重新打开文件并
fseek()定位(需文件为纯文本且每行长度相近)或基于偏移量读取 - 使用
WithoutOverlapping中间件防止同一文件被重复消费 - 记录处理状态到数据库(如
file_imports表),支持断点续传
其他关键细节
忽略这些易导致失败:
- CLI 模式默认禁用内存限制:
ini_set('memory_limit', '-1')可显式设置,但不能替代流式设计 - 关闭 Laravel 查询日志:
DB::disableQueryLog(),避免内存堆积 - 大事务会锁表、占内存,用
DB::transaction()包裹小批量操作(如每次 100 条),而非全文件 - CSV 编码问题:用
mb_convert_encoding()或iconv()统一转为 UTF-8,放在fgetcsv之前 - 文件锁:用
flock($fp, LOCK_SH)防止被其他进程修改,尤其在多命令并发时











