我有一个包含 100+ 百万行的表,我想将数据复制到另一个表中。我有1个要求, 1.查询执行不能阻塞对这些数据库表的其他操作, 我写了一个存储过程如下
我计算源表中的行数然后有一个循环但在每次迭代中复制 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 ;
查询在未锁定表的情况下执行,但在复制几百万行后变得太慢了。 请提出任何更好的方法来完成任务。 谢谢!
最佳答案
任何 INSERT ... SELECT ...
查询都会执行 acquire a SHARED lock在它从 SELECT 中的源表中读取的行上。但是通过处理较小的行 block ,锁不会持续太久。
使用 LIMIT ... OFFSET
的查询会随着您在源表中的推进而变得越来越慢。在每个 block 10,000 行时,您需要运行该查询 10,000 次,每次都必须重新开始并扫描整个表以达到新的 OFFSET。
无论您做什么,复制 1 亿行都需要一段时间。它做了很多工作。
我会使用 pt-archiver ,一款专为此目的而设计的免费工具。它以“ block ”(或子集)的形式处理行。它将动态调整 block 的大小,以便每个 block 需要 0.5 秒。
您的方法和 pt-archiver 之间最大的区别是 pt-archiver 不使用 LIMIT ... OFFSET
,它沿着主键索引遍历,而是按值选择行 block 的位置。所以每个 block 都被更有效地读取。
回复你的评论:
我预计,减小批量大小并增加迭代次数会使性能问题更糟,而不是更好。
原因是当您将 LIMIT
与 OFFSET
一起使用时,每个查询都必须从表的开头重新开始,并计算行数直到 >OFFSET
值。当您遍历表格时,它会变得越来越长。
使用 OFFSET
运行 20,000 个昂贵的查询将比运行 10,000 个类似的查询花费更长的时间。最昂贵的部分不是读取 5,000 或 10,000 行,也不是将它们插入到目标表中。昂贵的部分将一遍又一遍地跳过约 50,000,000 行。
相反,您应该按值而不是偏移量遍历表。
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 的工作方式。
关于MySql insert into select query复制1亿行太慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52099389/