Mysql插入选择查询太慢,无法复制1亿行

时间:2018-08-30 14:23:14

标签: mysql insert nonblocking

我有一个包含100+百万行的表,并想将数据复制到另一个表中。我有1个要求, 1.查询执行不得阻止对这些数据库表的其他操作, 我已经编写了如下存储过程

我计算到源表中的行数,然后进行循环,但是在每次迭代中复制10000行,开始事务并提交。然后按偏移量读取下一个10000。

$('#NbEmprunteurs').attr('value',data.nbEmprunteurs);

执行查询时不会锁定表,但是在复制了几百万行之后,查询变得太慢了。 请提出任何更好的方法来完成任务。 谢谢!

4 个答案:

答案 0 :(得分:1)

任何INSERT ... SELECT ...查询都会对它从SELECT中的源表读取的行进行acquire a SHARED lock。但是通过处理较小的行块,锁定不会持续太长时间。

使用LIMIT ... OFFSET进行查询时,随着在源表中的前进,查询的速度将越来越慢。以每块10,000行的速度,您需要运行该查询10,000次,每一次都必须重新开始并扫描表以达到新的OFFSET。

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

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

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


发表评论:

我希望缩小批处理大小并增加迭代次数将使性能问题变得更糟,

原因是,当将LIMITOFFSET一起使用时,每个查询都必须在表的开头重新开始,并计算直到OFFSET值的行。当您遍历表格时,时间会越来越长。

使用OFFSET运行20,000个昂贵的查询将比运行10,000个类似的查询花费更多的时间。最昂贵的部分将不会读取5,000或10,000行,也不会将其插入到目标表中。昂贵的部分将一遍又一遍地跳过约5000万行。

相反,您应该通过而不是通过偏移量遍历表。

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存档器的工作方式。

答案 1 :(得分:0)

块是有效词。希望您使用的是InnoDB(在记录级别阻止),而不是MyIsam(在表级别阻止)。不知道数据或底层硬件的复杂性,每个循环10K条记录可能太大。

答案 2 :(得分:0)

感谢@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 :(得分:0)

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