Laravel 从模型中插入数百万个数据库行
Laravel insert millions of database rows from models
我有一个文本文件,其中包含用逗号分隔的值,表示字符串中每一行的数据集。它们大约有 200 万个,我想解析字符串,从中创建 Laravel 模型并将每个模型作为一行存储在我的数据库中。
此时,我有一个class逐行解析文件并为每行创建一个模型如下:
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);
之类的方式删除超时,我最终会从浏览器收到一条 "failed to respond" 消息。
我的假设是需要进行某种分块,但老实说我不确定处理此卷的最佳方法。
提前感谢您提出任何建议。
我会创建一个 artisan 命令来处理导入,而不是通过浏览器执行此操作。你想让用户等到导入这个大文件吗?如果他移动使用后退按钮或关闭页面会怎样?
如果您想要或需要某种用户交互,例如用户上传文件并单击“导入”按钮,请将导入推送到 job queue,例如使用豆茎。前面提到的 artisan 将是 运行 并导入内容,如果完成,您可以向用户发送 e-mail 或松弛通知。如果您需要一些 UI 交互,您可以通过 ajax 发出请求,该脚本向 API 端点发出请求,请求导入状态或由于其异步,等待完成并显示一些 UI 通知,停止微调器或在错误情况下,显示错误消息。
我有一个文本文件,其中包含用逗号分隔的值,表示字符串中每一行的数据集。它们大约有 200 万个,我想解析字符串,从中创建 Laravel 模型并将每个模型作为一行存储在我的数据库中。
此时,我有一个class逐行解析文件并为每行创建一个模型如下:
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);
之类的方式删除超时,我最终会从浏览器收到一条 "failed to respond" 消息。
我的假设是需要进行某种分块,但老实说我不确定处理此卷的最佳方法。
提前感谢您提出任何建议。
我会创建一个 artisan 命令来处理导入,而不是通过浏览器执行此操作。你想让用户等到导入这个大文件吗?如果他移动使用后退按钮或关闭页面会怎样?
如果您想要或需要某种用户交互,例如用户上传文件并单击“导入”按钮,请将导入推送到 job queue,例如使用豆茎。前面提到的 artisan 将是 运行 并导入内容,如果完成,您可以向用户发送 e-mail 或松弛通知。如果您需要一些 UI 交互,您可以通过 ajax 发出请求,该脚本向 API 端点发出请求,请求导入状态或由于其异步,等待完成并显示一些 UI 通知,停止微调器或在错误情况下,显示错误消息。