如何从另一个表拷贝数据,而无需在MYSQL6.2锁表?

问题描述 投票:2回答:4

我有一个在MySQL服务器的所有历史数据的表格,这是非常庞大(约700万行)。我创建一个新表使用相同的列,但使用分区,然后我需要的所有数据从旧表复制到新的分区表。我已经得到了正确的脚本来做到这一点,但我认为它可能会锁定表。我不希望这种情况发生,因为这是生产服务器上。我应该怎么做,以避免锁定表?

mysql innodb
4个回答
3
投票

给该表有你可以做这样的事情完全一样的列:

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

我已经包括了基于Wistar's评论一些额外的解释。这里可以使用的读级别是:

  • READ承诺:一个有些象Oracle的隔离级别相对一致的(非锁定)读:每个一致读,即使在同一个事务,设置并读取它自己的新快照
  • 未提交读:SELECT语句在非锁定的方式执行,但可以使用一排的一个可能的早期版本。因此,使用这种隔离级别,这样的读取并不一致。这也被称为脏读。否则,这个隔离级别的工作原理是READ COMMITTED。
  • 重复读:这是InnoDB的默认隔离级别。对于持续读,有一个从读取已提交的隔离级别的一个重要区别:所有持续读在同一个事务读取由第一次读建立快照。这种约定意味着,如果您发出几个普通(非锁定)在同一事务中的SELECT语句,这些SELECT语句是一致的也相对于彼此。
  • SERIALIZABLE:这个级别是像REPEATABLE READ,但是InnoDB的隐式转换所有纯SELECT语句SELECT ... LOCK IN SHARE MODE如果自动提交被禁用。如果自动提交已启用,SELECT是它自己的事务。因此,它被称为是只读,如果为一致(非锁定)执行可以序列读取,不需要其他事务阻塞。 (要强制平原选择到方框如果其他事务已修改选定的行,禁止自动提交)。

我希望这有帮助。


0
投票

我不知道什么是你的脚本,但我建议你用吸盘插入。 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同时

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

如果有很多的差距,或者如果您有其他问题,则见other techniques for efficiently chunking

The evils of Pagination via OFFSET

更重要的是,是使用pt-online-schema-alter来自Percona的。它大部分的思维背后究竟我所描述的,再加上它允许您在副本正在做写入表。 (它使用TRIGGERs去实现它。)


0
投票

为了减少使用OFFSET的缺点,this article描述使用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 ;
© www.soinside.com 2019 - 2024. All rights reserved.