You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel高效导入万级以上CSV/XLS至数据库的技术咨询

解决Laravel批量CSV/XLS导入的性能与超时问题

嘿,咱来一步步搞定你遇到的这三个批量导入痛点:


1. 如何快速导入≥10000条记录的CSV/XLS至数据库?

你当前的代码是逐条创建模型并调用save(),这是效率最低的方式——每条记录都会触发一次独立的数据库查询,还会跑Laravel的模型事件(比如creating/created),1万条下来自然慢得离谱。推荐这几个优化方向:

方案一:用Laravel批量插入(性能提升最明显)

把所有数据整理成数组,一次性插入数据库,全程只生成1条SQL语句(或分块生成少量语句),速度能提升几十倍:

public function importDataSet(Request $request) {
    if($request->hasFile('file')){
        $file = $request->file('file');
        $uname = uniqid();
        $oname = $file->getClientOriginalName();
        $filename = $uname.$oname;
        $filelocation = 'data-sets';
        $filepath = $file->move($filelocation, $filename);
        
        ini_set('auto_detect_line_endings', TRUE);
        $rows = array_map('str_getcsv', file($filepath));
        array_shift($rows); // 移除表头
        
        // 整理批量插入的数据集
        $transactions = [];
        foreach ($rows as $row) {
            $transactions[] = [
                'dataset_id' => 1,
                'itemsets' => json_encode($row),
                'created_at' => now(),
                'updated_at' => now()
            ];
        }
        
        // 分块插入(避免SQL语句过长,每次插1000条)
        collect($transactions)->chunk(1000)->each(function($chunk) {
            Transaction::insert($chunk->toArray());
        });
    }
    return '导入完成!';
}

⚠️ 注意:批量插入不会触发模型的save()方法,所以模型事件不会执行,如果你的业务依赖这些事件,需要手动补充逻辑。

方案二:用第三方Excel处理包(推荐,更省心)

比如Maatwebsite/Laravel-Excel,它内置了分块读取、自动处理表头、批量插入的功能,还支持XLS/XLSX格式,代码更简洁:

// 先安装包:composer require maatwebsite/excel
use Maatwebsite\Excel\Facades\Excel;

public function importDataSet(Request $request) {
    if($request->hasFile('file')){
        Excel::import(new TransactionImport, $request->file('file'));
        return '导入完成!';
    }
    return '请选择要导入的文件';
}

// 创建Import类:app/Imports/TransactionImport.php
namespace App\Imports;

use App\Models\Transaction;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithChunkReading;

class TransactionImport implements ToModel, WithChunkReading
{
    public function model(array $row)
    {
        return new Transaction([
            'dataset_id' => 1,
            'itemsets' => json_encode($row)
        ]);
    }

    // 每次读取1000条,避免内存溢出
    public function chunkSize(): int
    {
        return 1000;
    }
}

方案三:临时关闭模型事件(如果必须用save())

如果你业务上需要保留save()逻辑,可以临时关闭模型的事件分发,减少额外开销:

// 临时关闭事件
Transaction::unsetEventDispatcher();

foreach ($rows as $row) {
    $transaction = new Transaction;
    $transaction->dataset_id = 1;
    $transaction->itemsets=json_encode($row);
    $transaction->save();
}

// 恢复事件分发
Transaction::setEventDispatcher(app('events'));

2. 使用set_time_limit(0)是否合理?有无更优方案?

set_time_limit(0)虽然能绕开PHP的超时限制,但非常不推荐,原因有两个:

  • 它会长时间占用Web服务器的进程,11分钟的请求会让服务器资源被占死,并发高的时候很容易崩溃;
  • 就算PHP不超时,Web服务器(比如Nginx、Apache)也有自己的超时时间(比如Nginx默认60秒),还是会返回504错误。

更优方案:用队列异步处理

把导入任务放到Laravel队列里,用户上传文件后直接返回成功,后台队列进程慢慢处理导入,体验和性能都拉满:

// 第一步:生成队列任务类
php artisan make:job ImportTransactionJob

// app/Jobs/ImportTransactionJob.php
namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\Transaction;

class ImportTransactionJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
    
    protected $filepath;

    public function __construct($filepath)
    {
        $this->filepath = $filepath;
    }

    public function handle()
    {
        ini_set('auto_detect_line_endings', TRUE);
        $rows = array_map('str_getcsv', file($this->filepath));
        array_shift($rows);
        
        $transactions = [];
        foreach ($rows as $row) {
            $transactions[] = [
                'dataset_id' => 1,
                'itemsets' => json_encode($row),
                'created_at' => now(),
                'updated_at' => now()
            ];
        }
        
        collect($transactions)->chunk(1000)->each(function($chunk) {
            Transaction::insert($chunk->toArray());
        });
        
        // 导入完成后删除临时文件
        unlink($this->filepath);
    }
}

// 第二步:控制器里分发任务
public function importDataSet(Request $request) {
    if($request->hasFile('file')){
        $file = $request->file('file');
        $uname = uniqid();
        $oname = $file->getClientOriginalName();
        $filename = $uname.$oname;
        $filelocation = 'data-sets';
        $filepath = $file->move($filelocation, $filename);
        
        // 把任务丢进队列
        ImportTransactionJob::dispatch($filepath);
        
        return '导入任务已提交,后台处理中,请稍后查看结果!';
    }
    return '请选择要导入的文件';
}

最后启动队列 worker:php artisan queue:work,导入任务就会在后台异步执行,用户不用傻等,服务器也不会被卡爆。


3. 能否在执行插入前预估所需最长时间?

可以做近似预估,原理是先测试一小部分数据的插入时间,再推算总时长。示例代码:

public function estimateImportTime(Request $request) {
    if($request->hasFile('file')){
        $file = $request->file('file');
        $filepath = $file->store('temp');
        
        ini_set('auto_detect_line_endings', TRUE);
        $rows = array_map('str_getcsv', file($filepath));
        array_shift($rows);
        $totalRows = count($rows);
        
        // 取前100条测试插入耗时
        $testRows = array_slice($rows, 0, 100);
        $start = microtime(true);
        
        $transactions = [];
        foreach ($testRows as $row) {
            $transactions[] = [
                'dataset_id' => 1,
                'itemsets' => json_encode($row),
                'created_at' => now(),
                'updated_at' => now()
            ];
        }
        Transaction::insert($transactions);
        
        $testTime = microtime(true) - $start;
        $estimatedTime = ($testTime / 100) * $totalRows;
        
        // 转换为易读的分秒格式
        $minutes = floor($estimatedTime / 60);
        $seconds = floor($estimatedTime % 60);
        
        return "预估导入时间:{$minutes}分{$seconds}秒(实际时长受服务器负载影响)";
    }
}

⚠️ 注意:这只是近似值,实际时间会受服务器CPU、数据库连接数、磁盘IO等因素影响,最好给用户一个范围(比如“预估5-10分钟”),而不是精确值。


内容的提问来源于stack exchange,提问作者Ezehlivinus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 06:35:36