【问题标题】:Fastest way to insert/update a million rows in Laravel 5.7在 Laravel 5.7 中插入/更新一百万行的最快方法
【发布时间】:2018-10-21 21:45:05
【问题描述】:

我正在使用 Laravel 5.7 从 API 服务器获取大量数据(大约 500k 行)并将其插入到表中(称为表 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。

【问题讨论】:

  • InnoDB 将每个 INSERT 直接写入磁盘,这会导致磁盘 i/o,启动事务并提交每 1000 或 2000 次插入可能会获得更高的性能。
  • 你在你的服务器上使用任何ssd吗?
  • innodb_buffer_pool_size 为 256M 对于 16G 服务器来说非常小 - 如果可用,从 8G 开始。进行批量插入意味着 innodb_log_file_size 也可能太小。为 2 的 innodb_flush_log_at_trx_commit 正在牺牲您的数据安全性以换取全局导入速度。一旦你批量插入,这应该不是问题,并且 innodb_flush_log_at_trx_commit 可以设置回 1。辅助表不会让你获得太多 AFAIK。

标签: php mysql laravel


【解决方案1】:

禁用autocommit 并在插入结束时手动提交

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

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

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

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

DB::beginTransaction()

// Your inserts here

DB::commit()

INSERT 与多个VALUES 一起使用

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

要使用 Laravel,您只需将一组值传递给 insert() 方法:

DB::table('your_table')->insert([
   [
       'column_a'=>'value',
       'column_b'=>'value',
   ],
   [
       'column_a'=>'value',
       'column_b'=>'value',
   ],
   [
       'column_a'=>'value',
       'column_b'=>'value',
   ],
]);

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

阅读文档

我在这篇文章中的两个 MySQL 文档链接都有大量关于提高 INSERT 速度的技巧。

避免使用 Laravel/PHP 插入

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

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

【讨论】:

    【解决方案2】:

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

    $users= array_chunk($data, 500, true);
    
    foreach ($users as $key => $user) {
      Model::insert($user);
    }
    

    取决于数据,也可以使用array_push()然后插入。

    【讨论】:

      【解决方案3】:

      不要在foreach() 中调用insert(),因为当你有n number of data 时,它会在数据库中执行n number of queries

      首先创建一个与数据库列名匹配的数据对象数组。然后将创建的数组传递给insert()函数。

      无论你有多少数据,这只会对数据库执行one查询。

      这速度更快,速度也更快。

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

      【讨论】:

      【解决方案4】:

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

      您可以通过对数组进行分块来做到这一点

      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 的大小,直到接近 DB 配置限制。确保留出一些安全余量(数据库限制的 0.6 倍)。

      使用 laravel,你不能比这更快。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-08-10
        • 1970-01-01
        • 2016-08-09
        • 2014-09-12
        • 2023-03-09
        • 1970-01-01
        相关资源
        最近更新 更多