繁体   English   中英

哪个更快:多个单插入还是一个多行插入?

[英]Which is faster: multiple single INSERTs or one multiple-row INSERT?

我正在尝试优化将数据插入 MySQL 的代码的一部分。 我应该链接 INSERT 来制作一个巨大的多行 INSERT 还是多个单独的 INSERT 更快?

https://dev.mysql.com/doc/refman/8.0/en/insert-optimization.html

插入一行所需的时间由以下因素决定,其中数字表示大致比例:

  • 连接: (3)
  • 向服务器发送查询:(2)
  • 解析查询:(2)
  • 插入行:(1 × 行大小)
  • 插入索引:(1 × 索引数)
  • 闭幕式:(1)

由此可以看出,发送一个大语句将为每个插入语句节省 7 的开销,在进一步阅读文本时还说:

如果您同时从同一客户端插入多行,请使用带有多个 VALUES 列表的 INSERT 语句一次插入多行。 这比使用单独的单行 INSERT 语句快得多(在某些情况下快很多倍)。

我知道我在回答这个问题之前有人问近两年半年后,但我只是想现在该节目确实做得每个刀片有多个值块是大大提供从项目我工作的一些硬数据比顺序的单个 VALUE 块 INSERT 语句更快。

我在 C# 中为此基准编写的代码使用 ODBC 将数据从 MSSQL 数据源(约 19,000 行,在任何写入开始之前读取)和 MySql .NET 连接器 (Mysql.Data.*) 内容读取到内存中通过准备好的语句将内存中的数据插入到 MySQL 服务器上的表中。 它的编写方式允许我动态调整每个准备好的 INSERT 的 VALUE 块的数量(即,一次插入 n 行,我可以在运行前调整 n 的值。)我还运行了测试每个 n 多次。

执行单个 VALUE 块(例如,一次 1 行)需要 5.7 - 5.9 秒的运行时间。 其他值如下:

一次 2 行:3.5 - 3.5 秒
一次 5 行:2.2 - 2.2 秒
一次 10 行:1.7 - 1.7 秒
一次 50 行:1.17 - 1.18 秒
一次 100 行:1.1 - 1.4 秒
一次 500 行:1.1 - 1.2 秒
一次 1000 行:1.17 - 1.17 秒

所以是的,即使只是将 2 或 3 个写入捆绑在一起也可以显着提高速度(运行时间减少 n 倍),直到您达到 n = 5 和 n = 10 之间的某个位置,此时改善显着下降,在 n = 10 到 n = 50 范围内的某个地方,改进变得可以忽略不计。

希望能帮助人们决定 (a) 是否使用 multiprepare 想法,以及 (b) 每个语句创建多少个 VALUE 块(假设您想要处理的数据可能足够大以将查询推送超过最大查询大小对于 MySQL,我认为在很多地方默认为 16MB,可能更大或更小,具体取决于服务器上设置的 max_allowed_pa​​cket 的值。)

一个主要因素是您是否使用事务引擎以及是否启用了自动提交。

默认情况下自动提交是开启的,你可能想保持开启; 因此,您所做的每个插入都会执行自己的事务。 这意味着如果您每行执行一次插入,您将为每一行提交一个事务。

假设是单线程,这意味着服务器需要为每一行同步一些数据到磁盘。 它需要等待数据到达持久存储位置(希望是 RAID 控制器中的电池供电内存)。 这本质上是相当缓慢的,并且可能会成为这些情况下的限制因素。

我当然假设您使用的是事务引擎(通常是 innodb)并且您没有调整设置以降低耐用性。

我还假设您使用单个线程来执行这些插入。 使用多线程会使事情变得有些混乱,因为某些版本的 MySQL 在 innodb 中有工作组提交 - 这意味着执行自己提交的多个线程可以共享对事务日志的单次写入,这很好,因为它意味着更少的同步到持久存储.

另一方面,结果是,您真的很想使用多行插入。

有一个限制,它会适得其反,但在大多数情况下,它至少为 10,000 行。 因此,如果您将它们批处理到 1,000 行,您可能是安全的。

如果您使用的是 MyISAM,还有很多其他的东西,但我不会让您厌烦这些。 和平。

一次通过电线发送尽可能多的插入。 实际插入速度应该相同,但您会看到网络开销减少带来的性能提升。

通常,对数据库的调用次数越少越好(意味着更快、更有效),因此请尝试以最小化数据库访问的方式编写插入代码。 请记住,除非您使用连接池,否则每次访问数据库都必须创建一个连接,执行 sql,然后断开连接。 相当多的开销!

你可能想要 :

  • 检查自动提交是否关闭
  • 打开连接
  • 在单个事务中发送多批插入(大小约为 4000-10000 行?你看)
  • 关闭连接

根据您的服务器扩展的程度(它对PostgreSQlOracleMSSQL绝对没问题),使用多个线程和多个连接执行上述操作。

通常,由于连接开销,多次插入会更慢。 一次执行多个插入将降低每个插入的开销成本。

根据您使用的语言,您可以在转到数据库之前用您的编程/脚本语言创建一个批处理并将每个插入添加到批处理中。 然后,您将能够使用一个连接操作来执行大批量。 这是Java 中一个示例。

MYSQL 5.5 一个 sql 插入语句花费了 ~300 到 ~450 毫秒。 而以下统计数据用于内联多个插入语句。

(25492 row(s) affected)
Execution Time : 00:00:03:343
Transfer Time  : 00:00:00:000
Total Time     : 00:00:03:343

我会说内联是要走的路:)

我只是做了一个小的基准测试,看起来对于很多线来说它并不快。 这是我插入 280 000 行的结果:

  • 由 10 000 : 164.96 秒
  • 由 5 000 : 37 秒
  • 按 1000 : 12.56 秒
  • 按 600 : 12.59 秒
  • 按 500 : 13.81 秒
  • 由 250 : 17.96 秒
  • 按 400:14.75 秒
  • 按 100 : 27 秒

看起来 1000 x 1000 是最好的选择。

在插入方面优化 Mysql 和 MariaDB 是多么荒谬。 我测试了 mysql 5.7 和 mariadb 10.3,它们没有真正的区别。

我已经在具有 NVME 磁盘、70,000 IOPS、1.1 GB/秒 seq 吞吐量的服务器上对此进行了测试,这可能是全双工(读取和写入)。
该服务器也是高性能服务器。
给它 20 GB 的内存。
数据库完全空了。

在进行多行插入时,我收到的速度是每秒 5000 次插入(尝试使用 1MB 到 10MB 的数据块)

现在的线索:
如果我添加另一个线程并插入到 SAME 表中,我突然有 2x5000 /秒。 多一个线程,我总共有 15000 个/秒

考虑一下:当执行 ONE 线程插入时,这意味着您可以按顺序写入磁盘(索引除外)。 使用线程时,实际上会降低可能的性能,因为它现在需要进行更多的随机访问。 但是现实检查表明 mysql 优化得非常糟糕,线程有很大帮​​助。

这种服务器的实际性能可能是每秒数百万,CPU 空闲磁盘空闲。
原因很明显,mariadb和mysql一样有内部延迟。

这是我做的一个小的 PHP 工作台的结果:

我正在尝试使用 PHP 8.0、MySQL 8.1 (mysqli) 以 3 种不同方式插入 3000 条记录

多个插入查询,具有多个事务:

$start = microtime(true);
for($i = 0; $i < 3000; $i++)
{
    mysqli_query($res, "insert into app__debuglog VALUE (null,now(), 'msg : $i','callstack','user','debug_speed','vars')");
}
$end = microtime(true);
echo "Took " . ($end - $start) . " s\n";

做了 5 次,平均: 11.132 秒(+/- 0.6 秒)

多个插入查询,单个事务:

$start = microtime(true);
mysqli_begin_transaction($res, MYSQLI_TRANS_START_READ_WRITE);
for($i = 0; $i < 3000; $i++)
{
    mysqli_query($res, "insert into app__debuglog VALUE (null,now(), 'msg : $i','callstack','user','debug_speed','vars')");
}
mysqli_commit($res);
$end = microtime(true);
echo "Took " . ($end - $start) . " ms\n";

5 次测试的结果: 0.48s (+/- 0.04s)

单个聚合插入查询

$start = microtime(true);

$values = "";

for($i = 0; $i < 3000; $i++)
{
    $values .= "(null,now(), 'msg : $i','callstack','user','debug_speed','vars')";
    if($i !== 2999)
        $values .= ",";
}
mysqli_query($res, "insert into app__debuglog VALUES $values");

$end = microtime(true);
echo "Took " . ($end - $start) . " ms\n";

5 次测试的结果: 0.085s (+/- 0.05s)

因此,对于 3000 行插入,看起来像:

  • 在单个写入事务中使用多个查询比为每个插入使用多个事务进行多个查询快约 22 倍。
  • 使用单个聚合插入语句仍然比在单个写入事务中使用多个查询快 6 倍

我会添加这样的信息,即一次过多的行取决于它们的内容可能会导致Got a packet 大于 'max_allowed_pa​​cket'

也许考虑使用像PHP 的 array_chunk这样的函数来为你的大数据集做多次插入。

多个插入速度更快,但它有阈值。 另一个 thrik 是禁用约束检查临时使插入快得多。 不管你的桌子有没有。 例如测试禁用外键并享受速度:

SET FOREIGN_KEY_CHECKS=0;

offcourse你应该在插入后重新打开它:

SET FOREIGN_KEY_CHECKS=1;

这是插入大量数据的常用方法。 数据完整性可能会中断,因此您应该在禁用外键检查之前注意这一点。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM