我有一个包含100+百万行的表,并想将数据复制到另一个表中。我有1个要求, 1.查询执行不得阻止对这些数据库表的其他操作, 我已经编写了如下存储过程
我计算到源表中的行数,然后进行循环,但是在每次迭代中复制10000行,开始事务并提交。然后按偏移量读取下一个10000。
$('#NbEmprunteurs').attr('value',data.nbEmprunteurs);
执行查询时不会锁定表,但是在复制了几百万行之后,查询变得太慢了。 请提出任何更好的方法来完成任务。 谢谢!
答案 0 :(得分:1)
任何INSERT ... SELECT ...
查询都会对它从SELECT中的源表读取的行进行acquire a SHARED lock。但是通过处理较小的行块,锁定不会持续太长时间。
使用LIMIT ... OFFSET
进行查询时,随着在源表中的前进,查询的速度将越来越慢。以每块10,000行的速度,您需要运行该查询10,000次,每一次都必须重新开始并扫描表以达到新的OFFSET。
无论您做什么,都要复制一亿行。它正在做很多工作。
我会使用pt-archiver,这是为此目的而设计的免费工具。它以“块”(或子集)处理行。它将动态调整块的大小,以便每个块花费0.5秒。
您的方法与pt-archiver之间的最大区别是pt-archiver不使用LIMIT ... OFFSET
,它沿主键索引移动,按值而不是按位置选择行块。因此,每个块的读取效率更高。
发表评论:
我希望缩小批处理大小并增加迭代次数将使性能问题变得更糟,
原因是,当将LIMIT
与OFFSET
一起使用时,每个查询都必须在表的开头重新开始,并计算直到OFFSET
值的行。当您遍历表格时,时间会越来越长。
使用OFFSET
运行20,000个昂贵的查询将比运行10,000个类似的查询花费更多的时间。最昂贵的部分将不会读取5,000或10,000行,也不会将其插入到目标表中。昂贵的部分将一遍又一遍地跳过约5000万行。
相反,您应该通过值而不是通过偏移量遍历表。
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存档器的工作方式。
答案 1 :(得分:0)
块是有效词。希望您使用的是InnoDB(在记录级别阻止),而不是MyIsam(在表级别阻止)。不知道数据或底层硬件的复杂性,每个循环10K条记录可能太大。
答案 2 :(得分: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$$
答案 3 :(得分:0)
对于我的设置和需求-我必须复制300至5亿行。 及时。在规格不合格的大型服务器上。
转储到“ csv”; 将结果分割成多个文件(我的情况是200k行是最佳的) 导入拆分文件
SELECT a.b.c INTO OUTFILE '/path/dumpfile.csv' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\n';
split -a 6 -l 200000 dumpfile.csv FileNamePrefix. // . allows numbering as ext
for f in ./*;
do
mysql -uUser -pPassword dbname -e "set autocommit = 0; set unique_checks = 0; set foreign_key_checks = 0; set sql_log_bin=0; LOAD DATA CONCURRENT INFILE '/path/to/split/files/"$f"' IGNORE INTO TABLE InputTableName FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' (a, b, c);commit;";
echo "Done: '"$f"' at $(date)";
done