如何在不锁定 MYSQL 6.2 中的 table 的情况下从另一个 table 复制数据?

How to copy data from another table without lock the table in MYSQL 6.2?

我有一个 table,它拥有 Mysql 服务器中的所有历史数据,而且它非常庞大(大约 7 亿行)。我正在创建一个具有相同列但分区的新 table,然后我需要将旧 table 中的所有数据复制到新分区 table 中。我已经有了正确的脚本来执行此操作,但我认为它可能会锁定 table。我不希望发生这种情况,因为它在生产服务器上。我应该怎么做才能避免锁定 table?

假定表格具有完全相同的列,您可以执行如下操作:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED ;
INSERT INTO NEW_TABLE (SELECT * FROM OLD_TABLE);
COMMIT ;

我根据 Wistar's 评论添加了一些额外的解释。这里可以使用的读取级别是:

  • READ COMMITTED:关于一致(非锁定)读取的有点类似于 Oracle 的隔离级别:每个一致读取,即使在同一事务中,设置和读取自己的新鲜快照
  • READ UNCOMMITTED:SELECT 语句以非锁定方式执行,但可能会使用行的可能早期版本。因此,使用这个隔离级别,这样的读取是不一致的。这也称为脏读。否则,此隔离级别的工作方式类似于 READ COMMITTED。
  • REPEATABLE READ:这是 InnoDB 的默认隔离级别。对于一致性读,与READ COMMITTED隔离级别有一个重要区别:同一个事务内的所有一致性读都读取第一次读建立的快照。此约定意味着如果您在同一事务中发出多个普通(非锁定)SELECT 语句,则这些 SELECT 语句也彼此一致。
  • SERIALIZABLE:这个级别类似于可重复读取,但 InnoDB 隐式地将所有普通 SELECT 语句转换为 SELECT ... 锁定共享模式,如果自动提交被禁用。如果启用了自动提交,则 SELECT 是它自己的事务。因此已知它是只读的,如果作为一致(非锁定)读取执行并且不需要为其他事务阻塞,则可以序列化。 (如果其他事务修改了选定的行,要强制普通 SELECT 阻塞,请禁用自动提交。)

希望对您有所帮助。

我不知道你的脚本是什么,但我建议你用卡盘插入。 See this example.

如果您使用 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED,您插入的行的版本可能不正确。如果你用一个选择插入所有行,那么你不仅会有锁而且它不会是最好的性能明智。

你可以做类似

 INSERT INTO NEW TABLE (SELECT * FROM OLD_TABLE LIMIT 1000 OFFSET 0 )
 INSERT INTO NEW TABLE (SELECT * FROM OLD_TABLE LIMIT 1000 OFFSET 1000 )
 INSERT INTO NEW TABLE (SELECT * FROM OLD_TABLE LIMIT 1000 OFFSET 2000 )
 ...

或使用 prepare statement 和 while

CREATE PROCEDURE myproc()
BEGIN
    @rows :=0
    SELECT COUNT(*) FROM OLD_TABLE into @rows
    DECLARE i int DEFAULT 0;
    WHILE i <= @rows DO
        PREPARE stmt1 FROM 'INSERT INTO NEW TABLE (SELECT * FROM OLD_TABLE LIMIT 1000 OFFSET ? )'
        EXECUTE stmt1 USING @i;
        DEALLOCATE PREPARE stmt1;
        SET i = i + 1000;
    END WHILE;
END

当然,您可以根据您的配置通过更改 LIMIT 大小

来调整块大小

分块复制。你有 AUTO_INCREMENT PRIMARY KEY 吗?如果是这样就做

 WHERE id >= $x AND id < $x + 1000

如果有很多差距,或者如果您有其他问题,请参阅 other techniques for efficiently chunking

The evils of Pagination via OFFSET.

更好的是,使用 Percona 的 pt-online-schema-alter。它完成了我所描述内容背后的大部分思考,plus 它允许您在复制完成时写入 table。 (它使用 TRIGGERs 来实现它。)

为了减少使用 OFFSET 的缺点,this article 描述了在数字主键 id 可用于强制使用时使用 JOIN 的可能方法适当的指数。请注意,为了跟踪进程,会创建一个 "procedure_log" table 并在处理一批后逐渐更新:

对于MySQL:

DROP PROCEDURE IF EXISTS copyTable;

DELIMITER |
CREATE PROCEDURE copyTable()
BEGIN

    DECLARE batchSize INT DEFAULT 100;
    DECLARE i INT DEFAULT 0;
    DECLARE rowCount INT;

    # Note that we use a WHERE clause to prevent a full table scan / use the index properly
    SET rowCount = (SELECT COUNT(id) FROM my_table WHERE id IS NOT NULL);

    CREATE TABLE IF NOT EXISTS my_table_copy LIKE my_table;
    CREATE TABLE IF NOT EXISTS procedure_log ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, entry TEXT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) );

    WHILE i <= rowCount DO
        INSERT IGNORE INTO my_table_copy (
          SELECT source.* FROM (
            SELECT id FROM my_table ORDER BY id LIMIT i, batchSize
          ) tmp
          JOIN my_table source ON source.id = tmp.id
          ORDER BY source.id
        );
        SET i = i + batchSize;

        INSERT INTO procedure_log (entry) VALUES (CONCAT('Copied batch from my_table => my_table_copy, batch: ', batchSize, ', offset: ', i, ', rowCount: ', rowCount));
    END WHILE;
END |
DELIMITER ;