在Laravel 5.7中插入/更新一百万行的最快方法

时间:2018-10-21 21:45:06

标签: php mysql laravel

我正在使用Laravel 5.7从API服务器获取大量数据(约50万行),并将其插入表的频率很高(称为表A)(至少每6个小时一次,24/7)-但是,下次插入时仅插入更改就足够了(但是至少有60-70%的项目会更改)。因此,该表将很快具有数千万行。

我想到了制作一个辅助表(称为表B)以将所有新数据存储到其中的想法。在将所有内容插入表A之前,我想将其与表B中的先前数据(使用Laravel,PHP)进行比较-因此,我将仅插入需要更新的记录。同样,通常它将占记录的60-70%。

我的第一个问题是,在这种情况下(显然,我想使其尽快完成),是否上述方法是首选方法?更新表中的记录将花费更多时间,并且会使表忙/锁定它。是否有更好的方法可以实现相同目的(意味着更新数据库中的记录)。


我面临的第二个问题是插入速度慢。现在,我正在使用本地环境(16GB RAM,I7-6920HQ CPU),MySQL插入行的速度非常慢(一次大约30-40条记录)。一行的大小约为50个字节。

我知道通过修改InnoDB的设置可以使其更快。但是,我也想以为我可以在Laravel方面做些事情来提高性能。

现在,我的Laravel代码如下所示(一次只能插入1条记录):

foreach ($response as $key => $value)
{
    DB::table('table_a')
        ->insert(
        [
            'test1' => $value['test1'],
            'test2' => $value['test2'],
            'test3' => $value['test3'],
            'test4' => $value['test4'],
            'test5' => $value['test5'],
        ]);
}

$response是一种数组。

所以我的第二个问题:有什么方法可以将记录的插入时间增加到大约50k /秒-在Laravel应用程序层(通过批量插入)和MySQL InnoDB级别(更改配置)。

当前的InnoDB设置:

innodb_buffer_pool_size        = 256M
innodb_log_file_size           = 256M
innodb_thread_concurrency      = 16
innodb_flush_log_at_trx_commit = 2
innodb_flush_method            = normal
innodb_use_native_aio = true

MySQL版本是5.7.21。

如果我忘记告诉/添加任何内容,请在评论中让我知道,我会尽快进行。

修改1: 我打算使用的服务器上将装有SSD-如果有任何区别。我认为MySQL插入仍将算作I / O。

4 个答案:

答案 0 :(得分:0)

请勿在{{1​​}}内部调用insert(),因为当您拥有foreach()时它将对数据库执行n number of queries

首先创建一个与数据库列名匹配的数据对象数组。然后将创建的数组传递给n number of data函数。

这将仅对数据库执行insert()查询,无论您拥有多少数据。

这快得多,太快了。

one

答案 1 :(得分:0)

感谢@Namoshek,我也遇到了同样的问题。解决方法是这样的。

$users= array_chunk($data, 500, true);

foreach ($users as $key => $user) {
  Model::insert($user);
}

根据数据,您还可以使用array_push()然后插入。

答案 2 :(得分:0)

禁用1,"Nicole",71,29,"Tusk" 2,"Bob",49,66,"Schiffer" 3,"Susan",61,17,"Tusk" 4,"Bob",24,59,"Trump" 5,"Nicole",25,46,"Goldberg" 6,"Bob",16,71,"Goldberg" 7,"Mark",43,43,"Schiffer" 并在插入结束时手动提交

根据MySQL 8.0文档。 (8.5.5 Bulk Data Loading for InnoDB Tables

您可以通过关闭自动提交来提高INSERT速度:

  
      
  • 将数据导入InnoDB时,请关闭自动提交模式,因为它会为每个插入操作将日志刷新到磁盘。要在导入操作期间禁用自动提交,请在其周围加上SET自动提交和COMMIT语句:
  •   
autocommit

在Laravel中执行此操作的其他方法是使用Database Transactions

    SET autocommit=0;
    ... SQL import statements ...
    COMMIT;

DB::beginTransaction() // Your inserts here DB::commit() 与多个INSERT一起使用

根据MySQL 8.0文档(8.2.5.1 Optimizing INSERT Statements),您还可以通过在单个插入语句上使用多个VALUES来优化INSERT速度。

要使用Laravel做到这一点,您只需将值数组传递给VALUES方法即可:

insert()

根据文档,它可以快很多倍。

阅读文档

我在这篇文章中发布的两个MySQL文档链接都包含大量有关提高INSERT速度的提示。

避免使用Laravel / PHP插入

如果您的数据源是(或可以是)CSV文件,则可以使用mysqlimport导入数据来更快地运行它。

使用PHP和Laravel从CSV文件导入数据是一项开销,除非您需要在插入之前进行一些数据处理。

答案 3 :(得分:-1)

您需要进行多行插入,但还需要对插入进行分块以不超过数据库限制

您可以通过对数组进行分块来实现

foreach (array_chunk($response, 1000) as $responseChunk)
{
    $insertableArray = [];
    foreach($responseChunk as $value) {
        $insertableArray[] = [
            'test1' => $value['test1'],
            'test2' => $value['test2'],
            'test3' => $value['test3'],
            'test4' => $value['test4'],
            'test5' => $value['test5'],
        ];
    }
    DB::table('table_a')->insert($insertableArray);
}

您可以增加块1000的大小,直到达到数据库配置限制为止。确保保留一些安全裕度(数据库限制的0.6倍)。

使用laravel不能比这更快。