我有一个包含大约 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 分钟以内)。
我考虑过的一些方法:
IDX_MY_DATA_S_NAME
索引,但我依靠唯一索引(IDX_MY_DATA_S_NAME_T_ID
)来保持数据干净。这是一个每天都会自动运行的过程,不可避免地会出现一些重复。另外,当我再次启用索引时,在这么大的数据集上重建索引似乎同样耗时。my_data
。我在某处看到了这个推荐,但经过思考,索引/PK 仍然是重新插入时的争论点。my_data
替换 temporary_data
听起来很有吸引力,但该表对于 s_id
字段有很多外键关系,所以我希望得到一些保证,这种方法值得禁用外键的麻烦,并且重新启用它们。子表包含的记录明显少于 my_data
,因此在这方面重新启用外键可能可以忽略不计。my_data
中,使所有字段NULL
当它不符合我最初在加载之前应用于temporary_data
的清理标准时变成my_data
。它很hacky,但它依赖于这样的假设:即使面对索引,LOAD DATA INFILE 也会比 INSERT...SELECT 更快,并且由于表上的唯一约束,运行后只会删除一行空值.这些听起来都不是很棒的想法。如果有人有任何建议,我会洗耳恭听。
摆脱
s_id
;它可能没有用。然后推广UNIQUE(t_id, s_name) as 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
值不会被烧毁。
每次插入多少行?如果是数百万的话,最好还是把它分成几块。请参阅我关于分块的讨论。它可能比建立一个巨大的撤销路径最终扔掉更快。
进一步讨论 -- 鉴于
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
我对大局和其他查询了解不够,无法判断采取哪个方向。所以我最初的建议(在“进一步讨论”之前)是“保守的”。