我有一个相当大的InnoDB表,其中包含大约1000万行(并且计数,预计将变为该大小的20倍)。每一行都不是那么大(平均每个131 B),但我不得不删除它们中的一大块,这需要很长时间。这是表结构:
CREATE TABLE `problematic_table` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`taxid` int(10) unsigned NOT NULL,
`blastdb_path` varchar(255) NOT NULL,
`query` char(32) NOT NULL,
`target` int(10) unsigned NOT NULL,
`score` double NOT NULL,
`evalue` varchar(100) NOT NULL,
`log_evalue` double NOT NULL DEFAULT '-999',
`start` int(10) unsigned DEFAULT NULL,
`end` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `taxid` (`taxid`),
KEY `query` (`query`),
KEY `target` (`target`),
KEY `log_evalue` (`log_evalue`)
) ENGINE=InnoDB AUTO_INCREMENT=7888676 DEFAULT CHARSET=latin1;
从表中删除大块的查询就像这样:
DELETE FROM problematic_table WHERE problematic_table.taxid = '57';
这样的查询花了将近一个小时才完成。我可以想象索引重写开销使得这些查询非常慢。
我正在开发一个可在预先存在的数据库上运行的应用程序。我很可能无法控制服务器变量,除非我强制要求对它们进行更改(我不愿意),所以我担心改变它们的建议没什么价值。
我已经尝试将INSERT ... SELECT
那些我不想删除的行放到临时表中而只删掉其余的行,但是当to-delete与to-keep的比率转向to-keep时,这不再是一个有用的解决方案。
这张表格可能会在将来经常看到INSERT
和SELECT
,但没有UPDATE
。基本上,它是一个日志和参考表,需要不时删除部分内容。
我可以通过限制它们的长度来改善此表上的索引吗?是否会切换到MyISAM帮助,在交易期间支持DISABLE KEYS
?还有什么可以尝试提高DELETE
表现?
编辑:一次这样的删除大约有一百万行。
答案 0 :(得分:23)
我有一个类似的场景,一个包含200万行的表和一个删除语句,它应该删除大约10万行 - 这需要大约10分钟才能完成。
检查配置后,我发现MySQL服务器运行时默认为innodb_buffer_pool_size
= 8 MB(!)。
以innodb_buffer_pool_size
= 1.5GB重新启动后,同样的情况需要10秒。
因此,如果“表的重新排序”可以适用于buffer_pool,那么看起来存在依赖关系。
答案 1 :(得分:12)
此解决方案一旦完成就可以提供更好的性能,但该过程可能需要一些时间才能实现。
可以添加新的BIT
列,默认为TRUE
为“有效”,FALSE
为“无效”。如果状态不够,您可以使用TINYINT
256个可能的值。
添加此新列可能需要很长时间,但是一旦结束,只要您使用PRIMARY
执行此操作,就像删除操作一样,更新速度应该快得多,并且不要将其编入索引新栏目。
InnoDB在如此庞大的表上花费这么长的DELETE
的原因是因为群集索引。它会根据您发现的PRIMARY
,首先UNIQUE
,或者如果无法找到PRIMARY
或UNIQUE
而确定为适当的替代品,对您的表进行实际订购,因此,当删除一行时,它现在在磁盘上物理重新排序整个表以进行速度和碎片整理。所以这不是DELETE
花了这么长时间;它是删除该行后的物理重新排序。
当你创建一个固定宽度的列并更新它而不是删除时,不需要在你的巨大的表中进行物理重新排序,因为行和表本身所占用的空间是不变的。
在非工作时间,可以使用单个DELETE
删除不必要的行。此操作仍然很慢,但总体上比删除单个行快得多。
答案 2 :(得分:1)
我有一个大约有2亿行的InnoDB表,但我确实遇到过同样的问题。 删除行花了很多时间。
表上有一个主键,一个唯一键和多个复合索引。
当删除较小的块时,它进行得非常快,因此我决定制作一个存储过程,该存储过程只是在有限的多次迭代中简单地删除了行。 有点像扬·拉尔森(Jan Larsen)的答案,但不需要单独的表格。
这使得在几分钟之内删除大块数据(约50万行)成为可能。
似乎InnoDB必须进行的事务才能回滚错误的更改,并且太大,因此无法放入内存,这导致删除操作非常糟糕。
过程:
CREATE DEFINER=`root`@`%` PROCEDURE `delete_rows`()
BEGIN
declare v_max int unsigned default 100;
declare v_counter int unsigned default 1;
while v_counter < v_max do
DELETE from items where a = 'A' AND b = 'B' AND c = 'C' LIMIT 10000;
set v_counter=v_counter+1;
end while;
END
然后通过以下方式调用它:
CALL delete_rows();
where句子与以a,b,c列开头的复合索引匹配,我认为这很重要,这样MySQL不必进行全表扫描即可匹配行。
答案 3 :(得分:0)
我通过使用存储过程解决了类似的问题,从而将性能提高了几千倍。
我的表有33M行和几个索引,我想删除10K行。我的数据库在Azure中,无法控制innodb_buffer_pool_size。
为简单起见,我创建了一个仅包含主tmp_id
字段的表id
:
CREATE TABLE `tmp_id` (
`id` bigint(20) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
)
我选择了一组我要删除的ID tmp_id
并运行delete from my_table where id in (select id from tmp_id);
这在12小时内没有完成,所以我尝试在tmp_id
中只使用一个ID花了25分钟。在几毫秒内完成delete from my_table where id = 1234
,所以我决定尝试在程序中执行此操作:
CREATE PROCEDURE `delete_ids_in_tmp`()
BEGIN
declare finished integer default 0;
declare v_id bigint(20);
declare cur1 cursor for select id from tmp_id;
declare continue handler for not found set finished=1;
open cur1;
igmLoop: loop
fetch cur1 into v_id;
if finished = 1 then leave igmLoop; end if;
delete from problematic_table where id = v_id;
end loop igmLoop;
close cur1;
END
现在call delete_ids_in_tmp();
在不到一分钟的时间内删除了所有10K行。
答案 4 :(得分:0)
DELETE FROM problematic_table WHERE problematic_table.taxid = '57';
删除引号,因为滑行是Integer,并且在引号中传递值使它成为字符串,由于Integer和String之间的比较,它不选择索引。
DELETE FROM problematic_table WHERE problematic_table.taxid = 57;