我正在尝试优化将数据插入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块比顺序单个VALUE块INSERT语句快得多 .
我在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的值 . )
一个主要因素是您是否使用事务引擎以及是否自动提交 .
默认情况下,自动提交已启用,您可能希望将其保留;因此,您执行的每个插入都会执行自己的事务 . 这意味着如果每行执行一次插入,那么您将为每一行提交一个事务 .
假设有一个线程,这意味着服务器需要将一些数据同步到光盘中 . 它需要等待数据到达持久存储位置(希望RAID控制器中的电池支持的RAM) . 这本质上相当缓慢,可能会成为这些情况的限制因素 .
我当然假设您正在使用事务引擎(通常是innodb)并且您没有调整设置以降低持久性 .
我还假设您使用单个线程来执行这些插入 . 使用多个线程会使事情变得混乱,因为某些版本的MySQL在innodb中具有工作组提交 - 这意味着执行自己提交的多个线程可以共享对事务日志的单个写入,这很好,因为这意味着与持久存储的同步更少 .
另一方面,结果是,您真的想要使用多行插入 .
它有一个限制,它会适得其反,但在大多数情况下它至少有10,000行 . 因此,如果您将它们分批最多1,000行,那么您可能很安全 .
如果你正在使用MyISAM,还有其他一些东西,但我不会厌倦你 . 和平 .
尽可能多次在线上发送多个插入 . 实际的插入速度应该是相同的,但是你会看到减少的性能提升网络开销 .
通常,对数据库的调用次数越少越好(意味着更快,更有效),因此尝试以最小化数据库访问的方式对插入进行编码 . 请记住,除非您使用连接池,否则每个数据库访问都必须创建连接,执行sql,然后拆除连接 . 相当多的开销!
你可能想要 :
检查自动提交是否已关闭
打开连接
在单个事务中发送多批插入(大小约为4000-10000行?您看到了)
关闭连接
根据服务器的扩展程度(最终确定 PostgreSQl , Oracle 和 MSSQL ),使用多个线程和多个连接执行上述操作 .
PostgreSQl
Oracle
MSSQL
通常,由于连接开销,多个插入将变慢 . 一次执行多个插入将降低每个插入的开销成本 .
根据您使用的语言,您可以在编程/脚本语言中创建批处理,然后再转到数据库并将每个插入添加到批处理中 . 然后,您将能够使用一个连接操作执行大批量 . Here's Java中的一个例子 .
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
我会说内联是要走的路:)
禁用约束检查使插入更快 . 无论你的 table 有没有,都无所谓 . 例如,测试禁用外键并享受速度:
SET FOREIGN_KEY_CHECKS=0;
在插入时,Mysql和MariaDB的优化程度是多么可笑 . 我测试了mysql 5.7和mariadb 10.3,没有真正的区别 .
我've tested this on a server with NVME disks, 70,000 IOPS, 1.1 GB/sec seq throughput and that'可能是全双工(读写) .服务器也是高性能服务器 .给它20 GB的内存 .数据库完全空了 .
我收到的速度是多行插入时每秒5000次插入(尝试使用1MB至10MB数据块)
Now the clue:如果我添加另一个线程并插入SAME表,我突然有2x5000 /秒 . 还有一个线程,我有15000总/秒
考虑一下:当执行一个线程插入时,它意味着您可以按顺序写入磁盘(索引除外) . 使用线程时,实际上会降低可能的性能,因为它现在需要进行更多的随机访问 . 但现实检查表明,mysql的优化程度非常高,以至于线程可以提供很多帮助 .
这种服务器的真正性能可能是每秒数百万,CPU空闲,磁盘空闲 .原因很清楚,mariadb就像mysql有内部延迟 .
10 回答
https://dev.mysql.com/doc/refman/8.0/en/insert-optimization.html
从这一点可以看出,发送一个大型语句将为每个插入语句节省7个开销,在进一步阅读文本时也会说:
我知道我现在正在努力表明,确实每个插入执行多个VALUE块比顺序单个VALUE块INSERT语句快得多 .
我在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的值 . )
一个主要因素是您是否使用事务引擎以及是否自动提交 .
默认情况下,自动提交已启用,您可能希望将其保留;因此,您执行的每个插入都会执行自己的事务 . 这意味着如果每行执行一次插入,那么您将为每一行提交一个事务 .
假设有一个线程,这意味着服务器需要将一些数据同步到光盘中 . 它需要等待数据到达持久存储位置(希望RAID控制器中的电池支持的RAM) . 这本质上相当缓慢,可能会成为这些情况的限制因素 .
我当然假设您正在使用事务引擎(通常是innodb)并且您没有调整设置以降低持久性 .
我还假设您使用单个线程来执行这些插入 . 使用多个线程会使事情变得混乱,因为某些版本的MySQL在innodb中具有工作组提交 - 这意味着执行自己提交的多个线程可以共享对事务日志的单个写入,这很好,因为这意味着与持久存储的同步更少 .
另一方面,结果是,您真的想要使用多行插入 .
它有一个限制,它会适得其反,但在大多数情况下它至少有10,000行 . 因此,如果您将它们分批最多1,000行,那么您可能很安全 .
如果你正在使用MyISAM,还有其他一些东西,但我不会厌倦你 . 和平 .
尽可能多次在线上发送多个插入 . 实际的插入速度应该是相同的,但是你会看到减少的性能提升网络开销 .
通常,对数据库的调用次数越少越好(意味着更快,更有效),因此尝试以最小化数据库访问的方式对插入进行编码 . 请记住,除非您使用连接池,否则每个数据库访问都必须创建连接,执行sql,然后拆除连接 . 相当多的开销!
你可能想要 :
检查自动提交是否已关闭
打开连接
在单个事务中发送多批插入(大小约为4000-10000行?您看到了)
关闭连接
根据服务器的扩展程度(最终确定
PostgreSQl
,Oracle
和MSSQL
),使用多个线程和多个连接执行上述操作 .通常,由于连接开销,多个插入将变慢 . 一次执行多个插入将降低每个插入的开销成本 .
根据您使用的语言,您可以在编程/脚本语言中创建批处理,然后再转到数据库并将每个插入添加到批处理中 . 然后,您将能够使用一个连接操作执行大批量 . Here's Java中的一个例子 .
MYSQL 5.5一个sql insert语句花了~300到~450ms . 而以下统计数据用于内联多个插入语句 .
我会说内联是要走的路:)
禁用约束检查使插入更快 . 无论你的 table 有没有,都无所谓 . 例如,测试禁用外键并享受速度:
在插入时,Mysql和MariaDB的优化程度是多么可笑 . 我测试了mysql 5.7和mariadb 10.3,没有真正的区别 .
我've tested this on a server with NVME disks, 70,000 IOPS, 1.1 GB/sec seq throughput and that'可能是全双工(读写) .
服务器也是高性能服务器 .
给它20 GB的内存 .
数据库完全空了 .
我收到的速度是多行插入时每秒5000次插入(尝试使用1MB至10MB数据块)
Now the clue:
如果我添加另一个线程并插入SAME表,我突然有2x5000 /秒 . 还有一个线程,我有15000总/秒
考虑一下:当执行一个线程插入时,它意味着您可以按顺序写入磁盘(索引除外) . 使用线程时,实际上会降低可能的性能,因为它现在需要进行更多的随机访问 . 但现实检查表明,mysql的优化程度非常高,以至于线程可以提供很多帮助 .
这种服务器的真正性能可能是每秒数百万,CPU空闲,磁盘空闲 .
原因很清楚,mariadb就像mysql有内部延迟 .