MySql插入select查询速度太慢,无法复制1亿行

MySql插入select查询速度太慢,无法复制1亿行,mysql,insert,nonblocking,Mysql,Insert,Nonblocking,我有一个由1亿多行组成的表,希望将数据复制到另一个表中。我有1个要求, 1.查询执行不得阻止对这些数据库表的其他操作, 我编写了一个存储过程,如下所示 我计算源表中的行数,然后创建一个循环,但在每次迭代中复制10000行,启动事务并提交它。然后按偏移量读取下一个10000 CREATE PROCEDURE insert_data() BEGIN DECLARE i INT DEFAULT 0; DECLARE iterations INT DEFAULT 0; DECLARE row

我有一个由1亿多行组成的表,希望将数据复制到另一个表中。我有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 ;
查询执行时没有锁定表,但在复制了数百万行之后,它变得太慢了。 请提出更好的方法来完成这项任务。
谢谢你

块是关键。希望您使用的是InnoDB,而不是MyIsam,它在记录级使用哪个块,在表级使用哪个块。由于不知道数据或底层硬件的复杂性,每个循环10K记录可能太大

任何插入。。。选择查询对从SELECT中的源表中读取的行执行。但是通过处理较小的行块,锁不会持续太长时间

具有限制的查询。。。当您在源表中前进时,偏移量将越来越慢。在每个块10000行的情况下,您需要运行10000次该查询,每个查询都必须重新开始并扫描整个表以达到新的偏移量

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

我会使用,一个免费的工具,专门为此设计。它以块或子集的形式处理行。它将动态调整块的大小,使每个块花费0.5秒

您的方法和pt archiver之间最大的区别是pt archiver不使用限制。。。偏移量,它沿着主键索引移动,按值而不是按位置选择行块。因此,可以更有效地读取每个块

请回复您的评论:

我预计,减小批处理大小——并增加迭代次数——将使性能问题变得更糟,而不是更好

原因是,在使用带偏移量的LIMIT时,每个查询都必须从表的开头重新开始,并按偏移量值计算行数。当您在表中迭代时,这会变得越来越长

使用OFFSET运行20000个昂贵的查询将比运行10000个类似查询花费更长的时间。最昂贵的部分不是读取5000或10000行,也不是将它们插入目标表。昂贵的部分将一次又一次地跳过约50000000行

相反,您应该通过值而不是偏移来迭代表

INSERT IGNORE INTO Table2(id, field2, field3)
        SELECT f1, f2, f3
        FROM Table1
        WHERE id BETWEEN rowOffset AND rowOffset+limitSize;
在循环之前,查询MINid和MAXid,从最小值开始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$$  

对于我的设置和需要-我必须复制3亿到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

谢谢你的回复。问题是我不能使用像pt archiver这样的第三方工具。你能对上述问题提出改进意见吗?从您的回复中我了解到的一件事是将限制从10000减少到任何更小的数字,比如5000,或者?是的,我正在使用inndb。我会试着把10k换成更小的数字,5000怎么样?