MySql insert into select 查询太慢,无法复制 1 亿行

sof*_*per 4 mysql insert nonblocking

我有一个由 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 ;
Run Code Online (Sandbox Code Playgroud)

该查询在不锁定表的情况下执行,但在复制几百万行后,它变得太慢了。请建议任何更好的方法来完成这项任务。感谢您!

Bil*_*win 6

任何INSERT ... SELECT ...查询都会在 SELECT 中从源表读取的行上获取 SHARED 锁。但通过处理较小的行块,锁不会持续太长时间。

\n\n

查询与LIMIT ... OFFSET将变得越来越慢。如果每个块有 10,000 行,则需要运行该查询 10,000 次,每次都必须重新开始并扫描表以到达新的 OFFSET。

\n\n

无论您做什么,复制 1 亿行都需要一段时间。它正在做很多工作。

\n\n

我会使用pt-archiver,这是一个为此目的设计的免费工具。它处理“块”(或子集)中的行。它将动态调整块的大小,以便每个块需要 0.5 秒。

\n\n

您的方法和 pt-archiver 之间最大的区别是 pt-archiver 不使用LIMIT ... OFFSET,它沿着主键索引行走,按值而不是按位置选择行块。因此每个块的读取效率都会更高。

\n\n
\n\n

回复您的评论:

\n\n

我预计,使批量大小变小 \xe2\x80\x94 并增加迭代次数 \xe2\x80\x94 将使性能问题变得更糟,而不是更好。

\n\n

原因是,当您使用LIMITwith时OFFSET,每个查询都必须从表的开头重新开始,并将行数计算到OFFSET值。当您迭代表时,它会变得越来越长。

\n\n

使用以下命令运行 20,000 个昂贵的查询OFFSET将比运行 10,000 个类似查询花费更长的时间。最昂贵的部分不是读取 5,000 或 10,000 行,也不是将它们插入到目标表中。昂贵的部分将一遍又一遍地跳过约 50,000,000 行。

\n\n

相反,您应该按值迭代表而不是偏移量

\n\n
INSERT IGNORE INTO Table2(id, field2, field3)\n        SELECT f1, f2, f3\n        FROM Table1\n        WHERE id BETWEEN rowOffset AND rowOffset+limitSize;\n
Run Code Online (Sandbox Code Playgroud)\n\n

循环之前,查询MIN(id)和MAX(id),然后开始rowOffset从最小值开始,循环到最大值。

\n\n

这就是 pt-archiver 的工作方式。

\n