构建 1,000M 行 MySQL 表

Ben*_*Ben 18 mysql files optimization performance-tuning

这个问题是从Stack Overflow转发的根据评论中的建议发布的,为重复道歉。

问题

问题 1:随着数据库表的大小变大,我如何调整 MySQL 以提高 LOAD DATA INFILE 调用的速度?

问题 2:使用一组计算机加载不同的 csv 文件,是提高性能还是杀死它?(这是我明天使用加载数据和批量插入的基准测试任务)

目标

我们正在为图像搜索尝试不同的特征检测器和聚类参数组合,因此我们需要能够及时构建大型数据库。

机器信息

这台机器有 256 gig 的 ram,如果有办法通过分发数据库来改善创建时间,还有另外 2 台具有相同数量的 ram 的机器吗?

表模式

表架构看起来像

+---------------+------------------+------+-----+---------+----------------+
| Field         | Type             | Null | Key | Default | Extra          |
+---------------+------------------+------+-----+---------+----------------+
| match_index   | int(10) unsigned | NO   | PRI | NULL    |                |
| cluster_index | int(10) unsigned | NO   | PRI | NULL    |                |
| id            | int(11)          | NO   | PRI | NULL    | auto_increment |
| tfidf         | float            | NO   |     | 0       |                |
+---------------+------------------+------+-----+---------+----------------+
Run Code Online (Sandbox Code Playgroud)

创建于

CREATE TABLE test 
(
  match_index INT UNSIGNED NOT NULL,
  cluster_index INT UNSIGNED NOT NULL, 
  id INT NOT NULL AUTO_INCREMENT,
  tfidf FLOAT NOT NULL DEFAULT 0,
  UNIQUE KEY (id),
  PRIMARY KEY(cluster_index,match_index,id)
)engine=innodb;
Run Code Online (Sandbox Code Playgroud)

到目前为止的基准测试

第一步是比较批量插入与从二进制文件加载到空表中。

It took:  0:09:12.394571  to do  4,000  inserts with 5,000 rows per insert
It took: 0:03:11.368320 seconds to load 20,000,000 rows from a csv file
Run Code Online (Sandbox Code Playgroud)

鉴于我从二进制 csv 文件加载数据时的性能差异,首先我使用下面的调用加载了包含 100K、1M、20M、200M 行的二进制文件。

LOAD DATA INFILE '/mnt/tests/data.csv' INTO TABLE test;
Run Code Online (Sandbox Code Playgroud)

2 小时后,我终止了 200M 行二进制文件(~3GB csv 文件)的加载。

所以我运行了一个脚本来创建表格,并从二进制文件中插入不同数量的行,然后删除表格,见下图。

在此处输入图片说明

从二进制文件中插入 1M 行大约需要 7 秒。接下来,我决定对一次插入 100 万行进行基准测试,以查看在特定数据库大小下是否会出现瓶颈。一旦数据库达到大约 59M 行,平均插入时间下降到大约 5,000/秒

在此处输入图片说明

设置全局 key_buffer_size = 4294967296 稍微提高了插入较小二进制文件的速度。下图显示了不同行数的速度

在此处输入图片说明

但是对于插入 1M 行,它并没有提高性能。

行:1,000,000 时间:0:04:13.761428 插入/秒:3,940

vs 空数据库

行数:1,000,000 时间:0:00:6.339295 次插入/秒:315,492

更新

使用以下顺序加载数据与仅使用加载数据命令

SET autocommit=0;
SET foreign_key_checks=0;
SET unique_checks=0;
LOAD DATA INFILE '/mnt/imagesearch/tests/eggs.csv' INTO TABLE test_ClusterMatches;
SET foreign_key_checks=1;
SET unique_checks=1;
COMMIT;
Run Code Online (Sandbox Code Playgroud) 在此处输入图片说明

因此,就正在生成的数据库大小而言,这看起来很有希望,但其他设置似乎不会影响加载数据 infile 调用的性能。

然后我尝试从不同的机器加载多个文件,但是 load data infile 命令锁定了表,因为文件很大,导致其他机器超时

ERROR 1205 (HY000) at line 1: Lock wait timeout exceeded; try restarting transaction
Run Code Online (Sandbox Code Playgroud)

增加二进制文件的行数

rows:  10,000,000  seconds rows:  0:01:36.545094  inserts/sec:  103578.541236
rows:  20,000,000  seconds rows:  0:03:14.230782  inserts/sec:  102970.29026
rows:  30,000,000  seconds rows:  0:05:07.792266  inserts/sec:  97468.3359978
rows:  40,000,000  seconds rows:  0:06:53.465898  inserts/sec:  96743.1659866
rows:  50,000,000  seconds rows:  0:08:48.721011  inserts/sec:  94567.8324859
rows:  60,000,000  seconds rows:  0:10:32.888930  inserts/sec:  94803.3646283
Run Code Online (Sandbox Code Playgroud)

解决方案:在 MySQL 之外预先计算 id 而不是使用自动增量

建立表

CREATE TABLE test (
  match_index INT UNSIGNED NOT NULL,
  cluster_index INT UNSIGNED NOT NULL, 
  id INT NOT NULL ,
  tfidf FLOAT NOT NULL DEFAULT 0,
  PRIMARY KEY(cluster_index,match_index,id)
)engine=innodb;
Run Code Online (Sandbox Code Playgroud)

用 SQL

LOAD DATA INFILE '/mnt/tests/data.csv' INTO TABLE test FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n';"
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

随着数据库大小的增长,使用脚本来预先计算索引似乎已经消除了性能损失。

更新 2 - 使用内存表

大约快 3 倍,不考虑将内存表移动到基于磁盘的表的成本。

rows:  0  seconds rows:  0:00:26.661321  inserts/sec:  375075.18851
rows:  10000000  time:  0:00:32.765095  inserts/sec:  305202.83857
rows:  20000000  time:  0:00:38.937946  inserts/sec:  256818.888187
rows:  30000000  time:  0:00:35.170084  inserts/sec:  284332.559456
rows:  40000000  time:  0:00:33.371274  inserts/sec:  299658.922222
rows:  50000000  time:  0:00:39.396904  inserts/sec:  253827.051994
rows:  60000000  time:  0:00:37.719409  inserts/sec:  265115.500617
rows:  70000000  time:  0:00:32.993904  inserts/sec:  303086.291334
rows:  80000000  time:  0:00:33.818471  inserts/sec:  295696.396209
rows:  90000000  time:  0:00:33.534934  inserts/sec:  298196.501594
Run Code Online (Sandbox Code Playgroud)

通过将数据加载到基于内存的表中,然后将其分复制到基于磁盘的表,通过查询复制 107,356,741 行的开销为 10 分 59.71 秒

insert into test Select * from test2;
Run Code Online (Sandbox Code Playgroud)

这使得加载 100M 行大约需要 15 分钟,这与直接将其插入基于磁盘的表大致相同。

sym*_*ean 4

好问题 - 解释得很好。

如何调整 MySQL 以提高 LOAD DATA INFILE 调用的速度?

您已经为密钥缓冲区设置了较高的设置 - 但这足够了吗?我假设这是一个 64 位安装(如果不是,那么您需要做的第一件事就是升级)并且不在 MSNT 上运行。运行一些测试后查看 mysqltuner.pl 的输出。

为了充分利用缓存,您可能会发现对输入数据进行批处理/预排序的好处(“排序”命令的最新版本具有许多用于对大型数据集进行排序的功能)。另外,如果您在 MySQL 之外生成 ID 号,那么它可能会更有效。

将使用一组计算机来加载不同的 csv 文件

假设(再次)您希望输出集表现为单个表,那么您将获得的唯一好处是通过分配排序和生成 id 的工作 - 您不需要更多的数据库。OTOH 使用数据库集群,您将遇到争用问题(除了性能问题之外,您不应该看到这种问题)。

如果您可以对数据进行分片并独立处理生成的数据集,那么是的,您将获得性能优势 - 但这并不能消除调整每个节点的需要。

检查您是否有至少 4 GB 的 sort_buffer_size。

除此之外,性能的限制因素都与磁盘 I/O 有关。有很多方法可以解决这个问题 - 但您可能应该考虑在 SSD 上使用一组镜像的条带数据集以获得最佳性能。