MySQL INSERT ... SELECT 4.2亿条记录的大数据集

时间:2018-10-26 00:54:38

标签: mysql bigdata large-data load-data-infile

我有大约4.2亿条记录的大型数据集,并且能够使用LOAD DATA INFILE语句将它们及时加载到临时表中,大约15分钟。我需要此临时表来暂存数据,因为在将数据加载到最终目的地之前,我需要对其进行一些清理。

临时表定义为:

CREATE TABLE `temporary_data` (
  `t_id` smallint(10) unsigned NOT NULL,
  `s_name` varchar(512) NOT NULL,
  `record_type` varchar(512) NOT NULL,
  `record_value` varchar(512) NOT NULL
) ENGINE=MyISAM;

需要加载此数据的目标表称为my_data,其定义为:

CREATE TABLE `my_data` (
  `s_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `t_id` smallint(10) unsigned NOT NULL,
  `s_name` varchar(63) NOT NULL,
  PRIMARY KEY (`s_id`),
  UNIQUE KEY `IDX_MY_DATA_S_NAME_T_ID` (`t_id`,`s_name`) USING BTREE,
  KEY `IDX_MY_DATA_S_NAME` (`s_name`) USING BTREE,
  CONSTRAINT `FK_MY_DATA_MY_PARENT` FOREIGN KEY (`t_id`) REFERENCES `my_parent` (`t_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

问题是,将数据从临时表加载到my_data的查询非常慢,因为我怀疑这是因为my_data包含两个索引和一个主键。到目前为止,此查询已运行6个小时以上:

INSERT IGNORE INTO my_data (t_id, s_name)
SELECT t_id, s_name
FROM temporary_data;

我需要确定一种加快查询速度的方法,以便及时完成查询(最好在30分钟以内)。

我考虑过的一些方法:

  1. 禁用索引::我也许可以禁用/删除IDX_MY_DATA_S_NAME索引,但是我依靠唯一索引(IDX_MY_DATA_S_NAME_T_ID)来保留数据清洁。这是一个日常过程,将自动运行,并且不可避免地会有一些重复。另外,当我再次启用索引时,似乎必须在如此大的数据集上重建索引也很耗时。
  2. 使用数据输出文件::将清理后的数据直接导出并重新导入到my_data中。我在某处看到了此建议,但考虑一下之后,索引/ PK仍然是重新插入的争论点。
  3. 交换表:my_data代替temporary_data听起来很吸引人,但是此表在s_id字段中有很多外键关系,所以我想一些确保此方法值得禁用外键并重新启用它们的麻烦。子表所包含的记录要比my_data少得多,因此在这方面重新启用外键可以忽略不计。
  4. 直接加载数据INFILE::使用语句的SET部分中的条件将数据直接加载到my_data中,以使所有字段NULL不符合清理条件我最初应用于temporary_data的条件,然后才将其加载到my_data中。它很hacky,但是它依赖于这样的假设:即使面对索引,LOAD DATA INFILE的速度也会比INSERT ... SELECT更快,由于表上的唯一约束,在它运行后将只删除一行空值。

这些听起来都不是很棒的主意。如果有人有任何建议,我将不知所措。

1 个答案:

答案 0 :(得分:1)

摆脱s_id,可能没有用。然后,升级UNIQUE(t_id, s_name) to be the PRIMARY KEY`。这样可以减少要为插入的每一行执行的测试数量。

考虑禁用FOREIGN KEYs;毕竟,他们需要执行可能多余的检查。

INSERT IGNORE INTO my_data (t_id, s_name)
    SELECT t_id, s_name
    FROM temporary_data
    ORDER BY t_id, s_name;  -- Add this

这样,插入程序就不会在目标表中跳来跳去,从而(希望)避免了很多I / O。

您要扩充表格吗?还是要更换它?如果要更换,还有更好的方法。

更多...

您是否注意到INSERT IGNORE为未插入的每一行浪费了一个AUTO_INCREMENT值?让我们尝试另一种方法...

INSERT INTO my_data (t_id, s_name)
    SELECT t.t_id, t.s_name
        FROM temporary_data AS t
        LEFT JOIN my_data AS m  USING(t_id, s_name)
        WHERE m.s_id IS NULL
        ORDER BY t.t_id, t.s_name;

ORDER BY避免在INSERT期间跳来跳去。
LEFT JOIN将活动限制为“新”行。
没有AUTO_INCREMENT值将被刻录。

每次将插入多少行?如果是数百万,则最好将其分成多个部分。请参阅我的discussion关于分块的信息。它 可能比建立一个巨大的撤消路径最终折腾要快。

进一步的讨论-已给出

my_data:  PRIMARY KEY(s_id)  -- and s_id is AUTO_INCREMENT
my_data:  INDEX(t_id, s_name)
INSERT...SELECT...ORDER BY (t_id, s_name)  -- same as index

这些很有效:

  • 由于ORDER BY和二级索引相同,因此将高效地添加索引。
  • 与此同时,新的AUTO_INCREMENT值将在表的“末尾”顺序生成。

更好的唯一情况是,(t_id, s_name)是唯一的。然后,我们可以考虑完全摆脱s_id,并将两个索引更改为该索引:

PRIMARY KEY(t_id, s_name)

如果其他表引用s_id,这将是一个问题。 可能的解决方法是保留s_id并拥有

PRIMARY KEY(t_id, s_name)
INDEX(s_id)   -- sufficient for AUTO_INCREMENT

我对总体情况和其他查询不够了解,无法判断应采取的方向。因此,我最初的建议(在“进一步讨论”之前)是“保守的”。