mysql插入选择4.2亿条记录的大型数据集

7rtdyuoh  于 2021-06-18  发布在  Mysql
关注(0)|答案(1)|浏览(362)

我有一个大约4.2亿条记录的大型数据集,我能够使用 LOAD DATA INFILE 声明。我需要这个临时表来暂存数据,因为在将数据加载到最终目的地之前,我对它进行了一些清理。
临时表定义为:

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分钟以下将是理想的)。
我考虑过的一些方法:
禁用索引:我也许可以通过禁用/删除 IDX_MY_DATA_S_NAME 索引,但我依赖于唯一的索引( IDX_MY_DATA_S_NAME_T_ID )保持数据的干净。这是一个每天都会自动运行的过程,不可避免地会有一些重复。另外,当我再次启用索引时,在这么大的数据集上重建索引似乎也同样耗时。
使用数据输出文件:将清理后的数据直接导出并重新导入到 my_data . 我在某个地方看到了这个建议,但仔细考虑之后,index/pk仍然是重新插入时的争论点。
交换表:替换 my_datatemporary_data 听起来很吸引人,但是这个表有很多外键关系 s_id 字段,所以我需要一些保证,这种方法将值得麻烦的禁用外键和重新启用它们。子表包含的记录将明显少于 my_data 因此,在这方面,重新启用外键可能可以忽略不计。
直接加载数据填充:将数据直接加载到 my_data 在语句的set部分使用条件使所有字段 NULL 当它不符合我最初应用的清理标准时 temporary_data 在将其装入 my_data . 这是黑客,但它是依靠的假设,加载数据填充将比插入更快。。。即使在编制索引时也要选择,由于表上的唯一约束,在运行之后也只需要删除一行空值。
这些听起来都不是什么好主意。如果有人有什么建议,我洗耳恭听。

yqlxgs2m

yqlxgs2m1#

摆脱 s_id ,可能是没用的。然后提升 UNIQUE(t_id, s_name) to be the 主键“”。这减少了为插入的每一行执行的测试的数量。
考虑禁用 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。
你在扩充table吗?或者替换它?如果替换,有一个更好的方法。
更多。。。
你注意到了吗 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 价值观将被烧毁。
每次插入多少行?如果它是数百万,那么最好把它分成小块。请看我关于分块的讨论。它可能比构建一个巨大的撤销路径来最终抛出要快。
进一步讨论——给出

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\u id并

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

我对大局和其他问题了解不够,无法判断应该朝哪个方向走。所以我最初的建议(在“进一步讨论”之前)是“保守的”。

相关问题