MySql insert into select 查询太慢,无法复制 1 亿行
MySql insert into select query is too slow to copy 100 million rows
我有一个 table 包含 100+ 百万行,我想将数据复制到另一个 table。我有1个要求,
1.查询执行不能阻塞对这些数据库table的其他操作,
我写了一个存储过程如下
我计算源中的行数 table 然后有一个循环但在每次迭代中复制 10000 行,启动事务并提交它。然后通过偏移量读取下一个 10000。
CREATE PROCEDURE insert_data()
BEGIN
DECLARE i INT DEFAULT 0;
DECLARE iterations INT DEFAULT 0;
DECLARE rowOffset INT DEFAULT 0;
DECLARE limitSize INT DEFAULT 10000;
SET iterations = (SELECT COUNT(*) FROM Table1) / 10000;
WHILE i <= iterations DO
START TRANSACTION;
INSERT IGNORE INTO Table2(id, field2, field3)
SELECT f1, f2, f3
FROM Table1
ORDER BY id ASC
LIMIT limitSize offset rowOffset;
COMMIT;
SET i = i + 1;
SET rowOffset = rowOffset + limitSize;
END WHILE;
END$$
DELIMITER ;
查询在未锁定 table 的情况下执行,但在复制几百万行后它变得太慢了。
请提出任何更好的方法来完成任务。
谢谢!
块是关键词。希望您使用的是 InnoDB(在记录级别阻塞)而不是 MyIsam(在 table 级别阻塞)。不知道数据或底层硬件的复杂性,每个循环 10K 条记录可能太大了。
任何 INSERT ... SELECT ...
查询都会对它从 SELECT 中的源 table 读取的行执行 acquire a SHARED lock。但是通过处理较小的行块,锁不会持续太久。
随着您在源 table 中前进,使用 LIMIT ... OFFSET
的查询会越来越慢。每个块 10,000 行,您需要 运行 查询 10,000 次,每次都必须重新开始并扫描 table 以到达新的 OFFSET。
无论您做什么,复制 1 亿行都需要一段时间。它做了很多工作。
我会使用 pt-archiver,一个专为此目的而设计的免费工具。它处理 "chunks" 中的行(或子集)。它会动态调整块的大小,使每个块需要 0.5 秒。
你的方法和 pt-archiver 最大的区别在于 pt-archiver 不使用 LIMIT ... OFFSET
,它沿着主键索引遍历,按值而不是按位置选择行块。所以每个块都被更有效地读取。
回复您的评论:
我预计减小批量大小——并增加迭代次数——会使性能问题更糟,而不是更好。
原因是,当您将 LIMIT
与 OFFSET
一起使用时,每个查询都必须从 table 的开头重新开始,并计算到 [= 的行数15=] 值。当您遍历 table.
时,它会变得越来越长
运行 20,000 个使用 OFFSET
的昂贵查询将比 运行ning 10,000 个类似查询花费更长的时间。最昂贵的部分不会读取 5,000 或 10,000 行,或将它们插入目标 table。昂贵的部分将一遍又一遍地跳过约 50,000,000 行。
相反,您应该通过 值 而不是偏移量来迭代 table。
INSERT IGNORE INTO Table2(id, field2, field3)
SELECT f1, f2, f3
FROM Table1
WHERE id BETWEEN rowOffset AND rowOffset+limitSize;
循环前查询MIN(id)和MAX(id),从rowOffset
最小值开始,循环到最大值
这就是 pt-archiver 的工作方式。
感谢@Bill Karvin
我按照你的建议删除了偏移量。以下查询效果非常好,
DROP PROCEDURE IF EXISTS insert_identifierdataset;
DELIMITER $$
CREATE PROCEDURE insert_data()
BEGIN
DECLARE i INT DEFAULT 0;
DECLARE limitSize INT DEFAULT 2000;
DECLARE maxId INT DEFAULT 0;
SET maxId = (SELECT MAX(id) FROM Table1);
WHILE i <= maxId DO
START TRANSACTION;
INSERT IGNORE INTO Table2(id, field1, field2)
SELECT id, field3, field4
FROM Table1
WHERE id> i
ORDER BY id ASC
LIMIT limitSize;
COMMIT;
SET i = i + limitSize;
END WHILE;
END$$
根据我的设置和需要 - 我必须复制 300 到 5 亿行。
及时。在大量低于规格的服务器上。
转储到"csv";
将结果拆分为多个文件(我的案例 200k 行是最佳的)
导入拆分文件
SELECT a.b.c INTO OUTFILE '/path/dumpfile.csv' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\n';
split -a 6 -l 200000 dumpfile.csv FileNamePrefix. // . allows numbering as ext
for f in ./*;
do
mysql -uUser -pPassword dbname -e "set autocommit = 0; set unique_checks = 0; set foreign_key_checks = 0; set sql_log_bin=0; LOAD DATA CONCURRENT INFILE '/path/to/split/files/"$f"' IGNORE INTO TABLE InputTableName FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' (a, b, c);commit;";
echo "Done: '"$f"' at $(date)";
done
我有一个 table 包含 100+ 百万行,我想将数据复制到另一个 table。我有1个要求, 1.查询执行不能阻塞对这些数据库table的其他操作, 我写了一个存储过程如下
我计算源中的行数 table 然后有一个循环但在每次迭代中复制 10000 行,启动事务并提交它。然后通过偏移量读取下一个 10000。
CREATE PROCEDURE insert_data()
BEGIN
DECLARE i INT DEFAULT 0;
DECLARE iterations INT DEFAULT 0;
DECLARE rowOffset INT DEFAULT 0;
DECLARE limitSize INT DEFAULT 10000;
SET iterations = (SELECT COUNT(*) FROM Table1) / 10000;
WHILE i <= iterations DO
START TRANSACTION;
INSERT IGNORE INTO Table2(id, field2, field3)
SELECT f1, f2, f3
FROM Table1
ORDER BY id ASC
LIMIT limitSize offset rowOffset;
COMMIT;
SET i = i + 1;
SET rowOffset = rowOffset + limitSize;
END WHILE;
END$$
DELIMITER ;
查询在未锁定 table 的情况下执行,但在复制几百万行后它变得太慢了。 请提出任何更好的方法来完成任务。 谢谢!
块是关键词。希望您使用的是 InnoDB(在记录级别阻塞)而不是 MyIsam(在 table 级别阻塞)。不知道数据或底层硬件的复杂性,每个循环 10K 条记录可能太大了。
任何 INSERT ... SELECT ...
查询都会对它从 SELECT 中的源 table 读取的行执行 acquire a SHARED lock。但是通过处理较小的行块,锁不会持续太久。
随着您在源 table 中前进,使用 LIMIT ... OFFSET
的查询会越来越慢。每个块 10,000 行,您需要 运行 查询 10,000 次,每次都必须重新开始并扫描 table 以到达新的 OFFSET。
无论您做什么,复制 1 亿行都需要一段时间。它做了很多工作。
我会使用 pt-archiver,一个专为此目的而设计的免费工具。它处理 "chunks" 中的行(或子集)。它会动态调整块的大小,使每个块需要 0.5 秒。
你的方法和 pt-archiver 最大的区别在于 pt-archiver 不使用 LIMIT ... OFFSET
,它沿着主键索引遍历,按值而不是按位置选择行块。所以每个块都被更有效地读取。
回复您的评论:
我预计减小批量大小——并增加迭代次数——会使性能问题更糟,而不是更好。
原因是,当您将 LIMIT
与 OFFSET
一起使用时,每个查询都必须从 table 的开头重新开始,并计算到 [= 的行数15=] 值。当您遍历 table.
运行 20,000 个使用 OFFSET
的昂贵查询将比 运行ning 10,000 个类似查询花费更长的时间。最昂贵的部分不会读取 5,000 或 10,000 行,或将它们插入目标 table。昂贵的部分将一遍又一遍地跳过约 50,000,000 行。
相反,您应该通过 值 而不是偏移量来迭代 table。
INSERT IGNORE INTO Table2(id, field2, field3)
SELECT f1, f2, f3
FROM Table1
WHERE id BETWEEN rowOffset AND rowOffset+limitSize;
循环前查询MIN(id)和MAX(id),从rowOffset
最小值开始,循环到最大值
这就是 pt-archiver 的工作方式。
感谢@Bill Karvin 我按照你的建议删除了偏移量。以下查询效果非常好,
DROP PROCEDURE IF EXISTS insert_identifierdataset;
DELIMITER $$
CREATE PROCEDURE insert_data()
BEGIN
DECLARE i INT DEFAULT 0;
DECLARE limitSize INT DEFAULT 2000;
DECLARE maxId INT DEFAULT 0;
SET maxId = (SELECT MAX(id) FROM Table1);
WHILE i <= maxId DO
START TRANSACTION;
INSERT IGNORE INTO Table2(id, field1, field2)
SELECT id, field3, field4
FROM Table1
WHERE id> i
ORDER BY id ASC
LIMIT limitSize;
COMMIT;
SET i = i + limitSize;
END WHILE;
END$$
根据我的设置和需要 - 我必须复制 300 到 5 亿行。 及时。在大量低于规格的服务器上。
转储到"csv"; 将结果拆分为多个文件(我的案例 200k 行是最佳的) 导入拆分文件
SELECT a.b.c INTO OUTFILE '/path/dumpfile.csv' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\n';
split -a 6 -l 200000 dumpfile.csv FileNamePrefix. // . allows numbering as ext
for f in ./*;
do
mysql -uUser -pPassword dbname -e "set autocommit = 0; set unique_checks = 0; set foreign_key_checks = 0; set sql_log_bin=0; LOAD DATA CONCURRENT INFILE '/path/to/split/files/"$f"' IGNORE INTO TABLE InputTableName FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' (a, b, c);commit;";
echo "Done: '"$f"' at $(date)";
done