我有一张MySQL服务器中保存所有历史数据的表格,它非常庞大(约有7亿行)。我正在创建一个具有相同列但具有分区的新表格,然后需要将旧表格中的所有数据复制到新的分区表格中。我已经得到了正确的脚本来完成这个任务,但我认为这可能会锁定表格。由于这是在生产服务器上进行操作,我不希望发生这种情况。我该怎么做才能避免锁定表格?
假设表格具有完全相同的列,您可以这样做:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED ;
INSERT INTO NEW_TABLE (SELECT * FROM OLD_TABLE);
COMMIT ;
根据Wistar的评论,我添加了一些额外的解释。可用的阅读级别包括:
我不知道你的脚本是什么,但我建议你使用分块插入。
如果你使用 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
,你可能会插入错误版本的行。如果你用一个select插入所有行,那么不仅会有锁,而且性能也不是最好的。
你可以像这样做:
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 )
...
或者使用准备语句和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
如果有很多间隔或其他问题,请参见其他有效分块技术。
更好的方法是使用Percona的pt-online-schema-alter
。它可以完成我所描述的大部分工作,而且在复制过程中允许您向表中写入数据。(它使用TRIGGERs
来实现。)
OFFSET
的劣势,this article描述了一种可能的方法,即在有数值主键id
的情况下使用JOIN
来强制使用正确的索引。请注意,为了跟踪进程,在处理批量后,会创建一个"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 ;
SHOW TABLE STATUS WHERE Name = 'name-of-your-table'
。引擎列中是什么?InnoDB?MyISAM? - Wistar