23

我有一个相当大的 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那些我不想删除到临时表中的行并只是删除其余的行,但是随着删除与保留的比率向保留转移,这不再是一个有用的解决方案.

这是一张将来可能会看到频繁INSERT的s和SELECTs,但没有UPDATEs的表。基本上,它是一个需要不时删除部分内容的日志记录和引用表。

我可以通过限制索引的长度来改进这张表上的索引吗?切换到DISABLE KEYS在交易期间支持的 MyISAM 会有所帮助吗?我还能尝试什么来提高DELETE性能?

编辑:一种这样的删除将是大约一百万行。

4

5 回答 5

30

我有一个类似的场景,一个有 200 万行的表和一个删除语句,它应该删除大约 10 万行 - 这样做大约需要 10 分钟。

检查配置后,我发现 MySQL 服务器正在以默认innodb_buffer_pool_size= 8 MB (!) 运行。

innodb_buffer_pool_size= 1.5GB 重新启动后,相同的场景需要 10 秒。

所以看起来“重新排序表”是否适合 buffer_pool 似乎存在依赖关系。

于 2013-10-23T15:25:46.347 回答
17

此解决方案在完成后可以提供更好的性能,但该过程可能需要一些时间来实施。

BIT可以添加一个新列,并将其默认TRUE为“活动”和FALSE“非活动”。如果这还不够状态,您可以使用TINYINT256 个可能的值。

添加这个新列可能需要很长时间,但是一旦完成,您的更新应该会快得多,只要您PRIMARY像处理删除一样执行它并且不索引这个新列。

InnoDB 之所以要花这么长时间来DELETE处理像您这样庞大的表是因为集群索引。它根据您的PRIMARY, 首先UNIQUE找到的,或者如果找不到PRIMARYor ,它可以确定为适当替代品的任何内容UNIQUE对您的表进行物理排序,因此,当删除一行时,它现在会在磁盘上对整个表进行物理重新排序以提高速度和碎片整理。所以这不是DELETE需要这么长时间的;这是删除该行后的物理重新排序。

当您创建一个固定宽度的列并对其进行更新而不是删除时,不需要在您的巨大表中进行物理重新排序,因为行和表本身消耗的空间是恒定的。

DELETE在下班时间,可以使用单个来删除不必要的行。此操作仍然很慢,但总体上比删除单个行要快得多。

于 2013-01-13T00:55:21.143 回答
3

我通过使用存储过程解决了类似的问题,从而将性能提高了数千倍。

我的表有 33M 行和几个索引,我想删除 10K 行。我的数据库在 Azure 中,无法控制 innodb_buffer_pool_size。

为简单起见,我创建了一个tmp_id只有主id字段的表:

CREATE TABLE `tmp_id` (
    `id` bigint(20) NOT NULL DEFAULT '0',
    PRIMARY KEY (`id`)
)

我选择了我想删除的一组 idtmp_id并运行了delete from my_table where id in (select id from tmp_id);这在 12 小时内没有完成,所以我只尝试了一个 idtmp_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 行。

于 2017-12-11T12:20:46.973 回答
3

我有一个包含大约 2 亿行的 InnoDB 表,我确实遇到了同样的问题。删除行需要很长时间。

表上有一个主键、一个唯一键和多个复合索引。

当以较小的块删除时,它的速度非常快,所以我决定创建一个存储过程,它可以在多次迭代中简单地删除行,但有限制。有点像 Jan Larsen 的回答,但不需要单独的表格。

这使得在几分钟内删除大量数据(大约 500K 行)成为可能。

似乎 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 不必进行全表扫描来匹配行。

于 2020-01-27T10:34:03.677 回答
-1
    DELETE FROM problematic_table WHERE problematic_table.taxid = '57';

删除引号,由于taxid 是整数并且在引号中传递值使其成为字符串,由于整数和字符串之间的比较,它不会选择索引。

    DELETE FROM problematic_table WHERE problematic_table.taxid = 57;
于 2019-12-09T06:54:53.530 回答