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

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

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

mysql innodb
5个回答
7
投票

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

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:此级别类似于 REPEATABLE READ,但如果禁用自动提交,InnoDB 会隐式地将所有普通 SELECT 语句转换为 SELECT ... LOCK IN SHARE MODE。如果启用自动提交,则 SELECT 是其自己的事务。因此,它是只读的,如果作为一致(非锁定)读取执行,则可以序列化,并且不需要阻止其他事务。 (如果其他事务已修改所选行,则要强制阻止普通 SELECT,请禁用自动提交。)

我希望这有帮助。


0
投票

我不知道你的脚本是什么,但我建议你用卡盘插入。 请参阅此示例

如果您使用

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 语句 和 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
大小

来调整块大小

0
投票

分块复制。 你有

AUTO_INCREMENT
PRIMARY KEY
吗? 如果是这样那就做吧

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

如果存在很多间隙,或者遇到其他问题,请参阅其他有效分块的技术

分页的弊端通过

OFFSET

更好的是使用 Percona 的

pt-online-schema-alter
。 它完成了我所描述的大部分思想,plus它允许您在复制完成时写入表。 (它使用
TRIGGERs
来实现它。)


0
投票

为了减少使用

OFFSET
的缺点,这篇文章 描述了一种在数字主键
JOIN
可用时使用
id
的可能方法,以强制使用正确的索引。请注意,为了跟踪流程,会创建一个“procedure_log”表,并在处理一批后逐渐更新:

对于 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 ;

0
投票

这就是我创建一个智能备份程序的方式,每当我调用它时都会复制所有表记录

DROP PROCEDURE IF EXISTS copyRawToBackup;

DELIMITER |
CREATE PROCEDURE copyRawToBackup()
BEGIN
    DECLARE batchSize INT DEFAULT 5000;
    DECLARE rowCount BIGINT;
    DECLARE startId BIGINT;
    DECLARE i BIGINT DEFAULT 0;
    CREATE TABLE IF NOT EXISTS `db`.`backup_table` LIKE `db`.`original_table`;
    SET rowCount = (SELECT `id` FROM `db`.`original_table` ORDER BY `id` DESC LIMIT 1);
    WHILE i <= rowCount DO
        SET startId = (SELECT `id` FROM `db`.`backup_table` ORDER BY `id` DESC LIMIT 1);
        INSERT INTO `db`.`backup_table` (SELECT * FROM `db`.`original_table` WHERE `id`>startId ORDER BY `id` LIMIT batchSize OFFSET i );
        SET i = i + batchSize;
    END WHILE;
END |
DELIMITER ;

CALL copyRawToBackup();
© www.soinside.com 2019 - 2024. All rights reserved.