我正在尝试优化将数据插入MySQL的代码的一部分。我应该将INSERT链接到一个巨大的多行INSERT还是多个单独的INSERT更快?
答案 0 :(得分:255)
https://dev.mysql.com/doc/refman/8.0/en/insert-optimization.html
插入行所需的时间由以下因素决定,其中数字表示大致比例:
- 连接:(3)
- 向服务器发送查询:(2)
- 解析查询:(2)
- 插入行:(1×行的大小)
- 插入索引:(1×索引数)
- 结束:(1)
从这一点可以看出,发送一个大型语句将为每个插入语句节省7个开销,在进一步阅读文本时也会说:
如果要同时从同一客户端插入多行,请使用带有多个VALUES列表的INSERT语句一次插入多行。与使用单独的单行INSERT语句相比,这要快得多(在某些情况下要快很多倍)。
答案 1 :(得分:141)
我知道在问到这个问题差不多两年半之后我就回答了这个问题,但我只是想提供一些我现在正在研究的项目的硬数据,这表明每个项目实际上有多个VALUE与顺序单个VALUE块INSERT语句相比,insert MUCH 更快。
我在C#中为此基准测试编写的代码使用ODBC将数据从MSSQL数据源读取到内存中(大约19,000行,所有内容都在开始编写之前读取)和MySql .NET连接器(Mysql.Data。* )通过预处理语句将数据从内存插入MySQL服务器上的表中。它的编写方式允许我动态调整每个准备好的INSERT的VALUE块的数量(即,一次插入n行,我可以在运行之前调整n的值。)我也运行了测试每个n多次。
执行单个VALUE块(例如,一次一行)需要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)是否使用多准备理念,以及(b)每个语句创建多少个VALUE块(假设您希望使用可能足够大的数据来推动查询超过MySQL的最大查询大小,我认为在很多地方默认为16MB,可能更大或更小,具体取决于服务器上设置的max_allowed_packet的值。)
答案 2 :(得分:17)
一个主要因素是您是否使用了事务引擎以及是否自动提交。
默认情况下,自动提交已启用,您可能希望将其保留;因此,您执行的每个插入都会执行自己的事务。这意味着如果每行执行一次插入,那么您将为每一行提交一个事务。
假设有一个线程,这意味着服务器需要将一些数据同步到光盘中。它需要等待数据到达持久存储位置(希望RAID控制器中的电池支持的RAM)。这本质上相当缓慢,可能会成为这些情况的限制因素。
我当然假设您正在使用事务引擎(通常是innodb)并且您没有调整设置以降低持久性。
我还假设您使用单个线程来执行这些插入操作。使用多个线程会使事情变得混乱,因为某些版本的MySQL在innodb中具有工作组提交 - 这意味着执行自己提交的多个线程可以共享对事务日志的单个写入,这很好,因为这意味着与持久存储的同步更少
另一方面,结果是,你真的想要使用多行插页。
有一个限制,它会适得其反,但在大多数情况下,它至少有10,000行。因此,如果您将它们分批最多1,000行,那么您可能很安全。
如果你正在使用MyISAM,还有其他一些东西,但我不会厌倦你。和平。
答案 3 :(得分:8)
尽可能多次在电线上发送插入数据。实际的插入速度应该相同,但是你会看到网络开销减少带来的性能提升。
答案 4 :(得分:7)
通常,对数据库的调用次数越少越好(意味着更快,更高效),因此尝试以最小化数据库访问的方式对插入进行编码。请记住,除非您使用连接池,否则每个数据库访问都必须创建连接,执行sql,然后拆除连接。相当多的开销!
答案 5 :(得分:4)
您可能想要:
根据服务器的扩展程度(PostgreSQl
,Oracle
和MSSQL
确定无效),使用多个线程和多个连接执行上述操作。
答案 6 :(得分:3)
通常,由于连接开销,多个插入会更慢。一次执行多个插入将减少每个插入的开销成本。
根据您使用的语言,您可以在编程/脚本语言中创建批处理,然后再转到数据库并将每个插入添加到批处理中。然后,您将能够使用一个连接操作执行大批量。 Here's Java中的一个例子。
答案 7 :(得分:3)
MYSQL 5.5 一个sql insert语句需要大约300到~450ms。 而以下统计数据用于内联多个插入语句。
(25492 row(s) affected)
Execution Time : 00:00:03:343
Transfer Time : 00:00:00:000
Total Time : 00:00:03:343
我会说inline是要走的路:)
答案 8 :(得分:2)
我只是做了一个小的基准测试,看起来对于很多线路来说它并不快。这是我插入 280 000 行的结果:
看起来 1000 x 1000 是最好的选择。
答案 9 :(得分:2)
这是我做的一个小 PHP bench 的结果:
我正在尝试使用 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 行的插入,看起来像:
答案 10 :(得分:1)
在插入时如何优化Mysql和MariaDB是荒谬的。 我测试了mysql 5.7和mariadb 10.3,两者之间没有真正的区别。
我已经在具有NVME磁盘,70,000 IOPS,1.1 GB /秒seq吞吐量的服务器上对此进行了测试,并且可能实现全双工(读取和写入)。
该服务器也是高性能服务器。
给它20 GB的内存。
数据库完全为空。
执行多行插入时,我收到的速度为每秒5000次插入(尝试使用1MB到10MB的数据块)
了解线索:
如果我添加另一个线程并插入到SAME表中,我突然会得到2x5000 / sec。
还有一个线程,我总共有15000个/秒
请考虑以下问题:在执行一个线程插入时,这意味着您可以顺序写入磁盘(索引例外)。 使用线程时,实际上会降低可能的性能,因为它现在需要执行更多的随机访问。 但是现实检查显示mysql的优化非常差,线程有很大帮助。
这种服务器可能实现的实际性能可能是每秒数百万,CPU处于空闲状态,磁盘处于空闲状态。
原因很明显,就像MySQL具有内部延迟一样,mariadb。
答案 11 :(得分:0)
多个插入更快,但它已经很好了。另一个thrik是禁用约束检查temprorary使插入更快。你的桌子有没有关系。例如,测试禁用外键并享受速度:
SET FOREIGN_KEY_CHECKS=0;
offcourse你应该在插入后重新打开:
SET FOREIGN_KEY_CHECKS=1;
这是插入大量数据的常用方法。 数据集成可能会中断,因此您必须在禁用外键检查之前对其进行处理。
答案 12 :(得分:0)
我会添加以下信息:根据行的内容,一次过多的行可能会导致 Got a packet bigger than 'max_allowed_packet'。
也许可以考虑使用 PHP's array_chunk 之类的函数为大型数据集进行多次插入。