【问题标题】:Laravel insert millions of database rows from modelsLaravel 从模型中插入数百万条数据库行
【发布时间】:2018-01-09 18:15:36
【问题描述】:

我有一个文本文件,其中包含用逗号分隔的值,这些值表示字符串中每一行的数据集。其中大约有 200 万个,我想解析字符串,从它们创建 Laravel 模型并将每个模型作为一行存储在我的数据库中。

此时,我有一个类,它逐行解析文件并为每个创建一个模型,如下所示:

class LargeFileParser{

    // File Reference
    protected $file;

    // Check if file exists and create File Object
    public function __construct($filename, $mode="r"){
        if(!file_exists($filename)){
            throw new Exception("File not found");
        }

        $this->file = new \SplFileObject($filename, $mode);
    }

    // Iterate through the text or binary document
    public function iterate($type = "Text", $bytes = NULL)
    {
        if ($type == "Text") {

            return new \NoRewindIterator($this->iterateText());

        } else {

            return new \NoRewindIterator($this->iterateBinary($bytes));
        }

    }

    // Handle Text iterations
    protected function iterateText()
    {
        $count = 0;

        while (!$this->file->eof()) {

            yield $this->file->fgets();

            $count++;
        }
        return $count;
    }

    // Handle binary iterations
    protected function iterateBinary($bytes)
    {
        $count = 0;

        while (!$this->file->eof()) {

            yield $this->file->fread($bytes);

            $count++;
        }
    }
}

然后我有一个控制器(我希望能够偶尔通过路由运行此迁移)来处理创建模型并将其插入数据库:

class CarrierDataController extends Controller
{
    // Store the data keys for a carrier model
    protected $keys;

    //Update the Carrier database with the census info
    public function updateData(){
        // File reference
        $file = new LargeFileParser('../storage/app/CENSUS.txt');

        //Get iterator for the file 
        $iterator = $file->iterate("Text");

        // For each iterator, store the data object as a carrier in the database
        foreach ($iterator as $index => $line) {   
            // First line sets the keys specified in the file 
            if($index == 0){
                $this->keys = str_getcsv(strtolower($line), ",", '"');
            }
            // The rest hold the data for each model
            else{                
                if ($index <= 100) {
                    // Parse the data to an array
                    $dataArray = str_getcsv($line, ",", '"');

                    // Get a data model
                    $dataModel = $this->createCarrierModel(array_combine($this->keys, $dataArray));

                    // Store the data
                    $this->storeData($dataModel);
                }
                else{
                    break;
                }
            }   
        }
    }

    // Return a model for the data
    protected function createCarrierModel($dataArray){

        $carrier = Carrier::firstOrNew($dataArray);

        return $carrier;
    }

    // Store the carrier data in the database
    protected function storeData($data){    
        $data->save();
    }
}

这很好用……也就是说,我将功能限制为 100 次插入。如果我删除这个检查并允许它在整个 200 万个数据集上运行这个函数,它就不再起作用了。要么有超时,要么如果我通过ini_set('max_execution_time', 6000); 之类的方式删除超时,我最终会从浏览器收到“响应失败”消息。

我的假设是需要进行某种分块,但老实说,我不确定处理此卷的最佳方法。

提前感谢您提出的任何建议。

【问题讨论】:

    标签: database laravel-5 laravel-eloquent


    【解决方案1】:

    我会创建一个工匠命令来处理导入,而不是通过浏览器执行此操作。你想让用户等到这个大文件被导入吗?如果他移动使用后退按钮或关闭页面会怎样?

    如果您想要或需要进行某种用户交互,例如用户上传文件并单击“导入”按钮,请将导入推送到 job queue,例如使用豆茎。上述工匠将运行并导入内容,如果完成,您可以向用户发送电子邮件或松弛通知。如果您需要一些 UI 交互,您可以通过 ajax 发出请求,并且该脚本向 API 端点发出请求,请求导入状态或由于其异步,等待完成并显示一些 UI 通知,停止微调器或在错误情况下,显示错误消息。

    【讨论】:

    • 我实际上看到无论如何你都可以从路由触发 Artisan 命令,所以这绝对是一个很好的解决方案,假设系统不会因为处理这么大的文件而超时。
    • 好的,这非常有效,非常感谢您的建议,这是一个很好的方法,因为我可以从路线触发它,它有效地完成了我的目标。
    • 很高兴听到:-)
    猜你喜欢
    • 2015-09-04
    • 1970-01-01
    • 2023-01-31
    • 1970-01-01
    • 1970-01-01
    • 2011-08-22
    • 2011-11-29
    • 1970-01-01
    • 2013-11-27
    相关资源
    最近更新 更多