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

问题描述 投票:0回答:3

我有一个包含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 ;

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

mysql insert nonblocking
3个回答
1
投票

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

当您在源表中前进时,LIMIT ... OFFSET的查询将变得越来越慢。每个块10,000行,您需要运行该查询10,000次,每个必须重新开始并扫描表以到达新的OFFSET。

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

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

你的方法和pt-archiver之间的最大区别在于pt-archiver不使用LIMIT ... OFFSET,它沿着主键索引,选择逐行而不是按位置的行。因此,每个块都可以更有效地读取。


你的评论:

我希望减小批量大小 - 并增加迭代次数 - 会使性能问题变得更糟,而不是更好。

原因是当你使用LIMITOFFSET时,每个查询都必须在表的开头重新开始,并将行计数到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的工作方式。


0
投票

Block是操作词。希望您使用InnoDB(在记录级别阻止)而不是MyIsam(在表级阻塞)。不知道数据的复杂性或下面的硬件,每个循环10K记录可能太大。


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$$  
© www.soinside.com 2019 - 2024. All rights reserved.