如何在大型InnoDB表上提高DELETE FROM性能?

时间:2013-01-11 18:16:07

标签: mysql performance innodb

我有一个相当大的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时,这不再是一个有用的解决方案。

这张表格可能会在将来经常看到INSERTSELECT,但没有UPDATE。基本上,它是一个日志和参考表,需要不时删除部分内容。

我可以通过限制它们的长度来改善此表上的索引吗?是否会切换到MyISAM帮助,在交易期间支持DISABLE KEYS?还有什么可以尝试提高DELETE表现?

编辑:一次这样的删除大约有一百万行。

5 个答案:

答案 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,或者如果无法找到PRIMARYUNIQUE而确定为适当的替代品,对您的表进行实际订购,因此,当删除一行时,它现在在磁盘上物理重新排序整个表以进行速度和碎片整理。所以这不是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;