MySQL 插入... SELECT 包含 4.2 亿条记录的大型数据集
MySQL INSERT... SELECT Large Dataset of 420 Million Records
我有一个包含大约 4.2 亿条记录的大型数据集,我能够使用 LOAD DATA INFILE
语句在大约 15 分钟的时间内将它们加载到临时 table 中。我需要这个临时 table 来暂存数据,因为我在将其加载到最终目的地之前对其进行了一些清理。
临时table定义为:
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;
需要加载此数据的目标 table 称为 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;
问题是,将数据从临时 table 加载到 my_data
的查询非常慢,我怀疑这是因为 my_data
包含两个索引和一个主索引钥匙。到目前为止,这个查询已经运行宁了 6 个多小时:
INSERT IGNORE INTO my_data (t_id, s_name)
SELECT t_id, s_name
FROM temporary_data;
我需要确定一种方法来加快此查询的速度,以便及时完成(最好在 30 分钟以内)。
我考虑过的一些方法:
- 禁用索引: 我也许可以使用 disabling/removing
IDX_MY_DATA_S_NAME
索引,但我依赖于唯一索引(IDX_MY_DATA_S_NAME_T_ID
) 以保持数据清洁。这是一个每天都会自动 运行 的过程,不可避免地会有一些重复。另外,当我再次启用索引时,在如此大的数据集上重建索引似乎同样耗时。
- 使用 DATA OUTFILE: 将清理后的数据直接导出并重新导入到
my_data
。我在某处看到了这个推荐,但在考虑之后,indexes/PK仍然是重新插入的争论点。
- 交换 tables: 用
temporary_data
替换 my_data
听起来很吸引人,但是这个 table 有很多外键s_id
字段的关系,所以我想要一些保证这种方法值得禁用外键并重新启用它们的麻烦。子 table 包含的记录比 my_data
少得多,因此在这方面重新启用外键可能可以忽略不计。
- LOAD DATA INFILE directly: load data directly into
my_data
using conditionals in the SET part of the statement to make all fields NULL
when it在将其加载到 my_data
之前,不符合我最初应用于 temporary_data
的清理标准。这是 hacky 但它依赖于加载数据 INFILE 将比 INSERT 更快的假设... SELECT 即使面对索引,并且在它之后只有一行空值要删除 运行 s 由于 table. 的唯一约束
None 这些想法听起来非常棒。如果有人有任何提示,我会洗耳恭听。
去掉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
这样,插入内容就不会在目标 table 中跳来跳去,从而(希望)避免很多 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
将 activity 限制为 "new" 行。
没有 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
值将在 table 的 "end" 上依次生成。
唯一更好的是如果 (t_id, s_name)
是唯一的。那我们可以考虑完全去掉s_id
,把两个索引改成这个:
PRIMARY KEY(t_id, s_name)
如果其他 table 引用 s_id
,这将是一个问题。 可能的 解决方法是保持 s_id 并拥有
PRIMARY KEY(t_id, s_name)
INDEX(s_id) -- sufficient for AUTO_INCREMENT
我对大局和其他问题的了解还不够,无法判断应该采取哪个方向。所以我最初的建议(在 "Further discussion" 之前)是 'conservative'.
我有一个包含大约 4.2 亿条记录的大型数据集,我能够使用 LOAD DATA INFILE
语句在大约 15 分钟的时间内将它们加载到临时 table 中。我需要这个临时 table 来暂存数据,因为我在将其加载到最终目的地之前对其进行了一些清理。
临时table定义为:
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;
需要加载此数据的目标 table 称为 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;
问题是,将数据从临时 table 加载到 my_data
的查询非常慢,我怀疑这是因为 my_data
包含两个索引和一个主索引钥匙。到目前为止,这个查询已经运行宁了 6 个多小时:
INSERT IGNORE INTO my_data (t_id, s_name)
SELECT t_id, s_name
FROM temporary_data;
我需要确定一种方法来加快此查询的速度,以便及时完成(最好在 30 分钟以内)。
我考虑过的一些方法:
- 禁用索引: 我也许可以使用 disabling/removing
IDX_MY_DATA_S_NAME
索引,但我依赖于唯一索引(IDX_MY_DATA_S_NAME_T_ID
) 以保持数据清洁。这是一个每天都会自动 运行 的过程,不可避免地会有一些重复。另外,当我再次启用索引时,在如此大的数据集上重建索引似乎同样耗时。 - 使用 DATA OUTFILE: 将清理后的数据直接导出并重新导入到
my_data
。我在某处看到了这个推荐,但在考虑之后,indexes/PK仍然是重新插入的争论点。 - 交换 tables: 用
temporary_data
替换my_data
听起来很吸引人,但是这个 table 有很多外键s_id
字段的关系,所以我想要一些保证这种方法值得禁用外键并重新启用它们的麻烦。子 table 包含的记录比my_data
少得多,因此在这方面重新启用外键可能可以忽略不计。 - LOAD DATA INFILE directly: load data directly into
my_data
using conditionals in the SET part of the statement to make all fieldsNULL
when it在将其加载到my_data
之前,不符合我最初应用于temporary_data
的清理标准。这是 hacky 但它依赖于加载数据 INFILE 将比 INSERT 更快的假设... SELECT 即使面对索引,并且在它之后只有一行空值要删除 运行 s 由于 table. 的唯一约束
None 这些想法听起来非常棒。如果有人有任何提示,我会洗耳恭听。
去掉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
这样,插入内容就不会在目标 table 中跳来跳去,从而(希望)避免很多 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
将 activity 限制为 "new" 行。
没有 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
值将在 table 的 "end" 上依次生成。
唯一更好的是如果 (t_id, s_name)
是唯一的。那我们可以考虑完全去掉s_id
,把两个索引改成这个:
PRIMARY KEY(t_id, s_name)
如果其他 table 引用 s_id
,这将是一个问题。 可能的 解决方法是保持 s_id 并拥有
PRIMARY KEY(t_id, s_name)
INDEX(s_id) -- sufficient for AUTO_INCREMENT
我对大局和其他问题的了解还不够,无法判断应该采取哪个方向。所以我最初的建议(在 "Further discussion" 之前)是 'conservative'.