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

问题描述 投票:0回答:1

我有一个包含大约 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. 直接LOAD DATA INFILE:使用语句SET部分中的条件将数据直接加载到
    my_data
    中,使所有字段
    NULL
    当它不符合我最初在加载之前应用于
    temporary_data
    的清理标准时变成
    my_data
    。它很hacky,但它依赖于这样的假设:即使面对索引,LOAD DATA INFILE 也会比 INSERT...SELECT 更快,并且由于表上的唯一约束,运行后只会删除一行空值.

这些听起来都不是很棒的想法。如果有人有任何建议,我会洗耳恭听。

mysql bigdata large-data load-data-infile
1个回答
2
投票

摆脱

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

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

© www.soinside.com 2019 - 2024. All rights reserved.