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

Rad*_*ity 7 php mysql laravel

我正在使用 Laravel 5.7 从 API 服务器获取大量数据(大约 50 万行)并将其插入表(称为表 A)非常频繁(至少每六小时,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'],
        ]);
}
Run Code Online (Sandbox Code Playgroud)

$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
Run Code Online (Sandbox Code Playgroud)

MySQL 版本是 5.7.21。

如果我忘记告诉/添加任何内容,请在评论中告诉我,我会尽快完成。

编辑 1: 我计划使用的服务器上将装有 SSD - 如果这有什么不同的话。我认为 MySQL 插入仍将算作 I/O。

Tha*_*han 6

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

首先创建与数据库列名称匹配的数据对象数组。然后将创建的数组传递给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);
Run Code Online (Sandbox Code Playgroud)

  • 不过,对 50 万行执行此操作可能具有挑战性。我建议将插入分块。 (8认同)
  • 对结果进行分块并使用雄辩的集合也可能会有所帮助:https://laravel.com/docs/7.x/collections#method-chunk (2认同)

uda*_*125 6

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

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

foreach ($users as $key => $user) {
  Model::insert($user);
}
Run Code Online (Sandbox Code Playgroud)

根据数据情况,也可以使用array_push(),然后插入。


Eli*_*res 6

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

根据 MySQL 8.0 文档。( 8.5.5 InnoDB 表的批量数据加载)

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

  • 将数据导入 InnoDB 时,请关闭自动提交模式,因为它会为每次插入执行日志刷新到磁盘。要在导入操作期间禁用自动提交,请使用 SET autocommit 和 COMMIT 语句将其括起来:
    SET autocommit=0;
    ... SQL import statements ...
    COMMIT;
Run Code Online (Sandbox Code Playgroud)

在 Laravel 中执行此操作的其他方法是使用数据库事务

DB::beginTransaction()

// Your inserts here

DB::commit()
Run Code Online (Sandbox Code Playgroud)

INSERT与多个一起使用VALUES

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

要使用 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',
   ],
]);
Run Code Online (Sandbox Code Playgroud)

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

阅读文档

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

避免使用 Laravel/PHP 插入它

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

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