更快的方法来删除匹配的行?

谈到数据库,我是一个相对的新手。 我们正在使用MySQL,目前我正在尝试加速SQL语句,似乎需要一段时间才能运行。 我环顾了一下类似的问题,但没有find一个。

目标是删除表A中与表B中匹配的所有行。

我目前正在执行以下操作:

DELETE FROM a WHERE EXISTS (SELECT b.id FROM b WHERE b.id = a.id); 

表a中约有100K行,表b中约有22K行。 列'id'是两个表的PK。

这个陈述需要大约3分钟的时间运行在我的testing盒 – 奔腾D,XP SP3,2GB内存,MySQL 5.0.67。 这对我来说似乎很慢。 也许不是,但我希望能加快速度。 有没有更好/更快的方法来完成这个?


编辑:

一些额外的信息,可能会有所帮助。 表A和B具有相同的结构,我已经做了以下创build表B:

 CREATE TABLE b LIKE a; 

表a(也就是表b)有几个索引来帮助加快对它的查询。 再次,我是DB工作的相对新手,仍然在学习。 我不知道这对事物有多大影响,如果有的话。 我认为它也有效果,因为索引也必须清理,对吧? 我也想知道是否有任何其他数据库设置,可能会影响速度。

另外,我正在使用INNO DB。


以下是一些可能对您有帮助的其他信息。

表A有一个类似的结构(我已经消毒了一下):

 DROP TABLE IF EXISTS `frobozz`.`a`; CREATE TABLE `frobozz`.`a` ( `id` bigint(20) unsigned NOT NULL auto_increment, `fk_g` varchar(30) NOT NULL, `h` int(10) unsigned default NULL, `i` longtext, `j` bigint(20) NOT NULL, `k` bigint(20) default NULL, `l` varchar(45) NOT NULL, `m` int(10) unsigned default NULL, `n` varchar(20) default NULL, `o` bigint(20) NOT NULL, `p` tinyint(1) NOT NULL, PRIMARY KEY USING BTREE (`id`), KEY `idx_l` (`l`), KEY `idx_h` USING BTREE (`h`), KEY `idx_m` USING BTREE (`m`), KEY `idx_fk_g` USING BTREE (`fk_g`), KEY `fk_g_frobozz` (`id`,`fk_g`), CONSTRAINT `fk_g_frobozz` FOREIGN KEY (`fk_g`) REFERENCES `frotz` (`g`) ) ENGINE=InnoDB AUTO_INCREMENT=179369 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; 

我怀疑这个问题的一部分是这个表有很多索引。 表B类似于表B,尽pipe它只包含列idh

另外,分析结果如下:

 starting 0.000018 checking query cache for query 0.000044 checking permissions 0.000005 Opening tables 0.000009 init 0.000019 optimizing 0.000004 executing 0.000043 end 0.000005 end 0.000002 query end 0.000003 freeing items 0.000007 logging slow query 0.000002 cleaning up 0.000002 

解决了

感谢所有的回应和评论。 他们当然让我去思考这个问题。 对dotjoe的荣誉,让我摆脱这个问题,通过问简单的问题“做任何其他表引用a.id?

问题是在表A上有一个DELETE TRIGGER,它调用一个存储过程来更新另外两个表,C和D.表C有一个FK返回到a.id,并在存储过程中做了一些与该id有关的东西它有这样的说法,

 DELETE FROM c WHERE c.id = theId; 

我查看了EXPLAIN语句,并将其重写为:

 EXPLAIN SELECT * FROM c WHERE c.other_id = 12345; 

所以,我可以看到这是做什么,它给了我以下信息:

 id 1 select_type SIMPLE table c type ALL possible_keys NULL key NULL key_len NULL ref NULL rows 2633 Extra using where 

这告诉我,这是一个痛苦的操作,因为它被称为22500次(对于给定的数据集被删除),那就是问题所在。 一旦我在该other_id列创build了一个INDEX并重新解释,我得到了:

 id 1 select_type SIMPLE table c type ref possible_keys Index_1 key Index_1 key_len 8 ref const rows 1 Extra 

好多了,其实真的很棒。

我添加了Index_1和我的删除时间与mattkemp报告的时间一致 。 这是我的一个非常微妙的错误,因为在最后一分钟鞋子有一些额外的function。 事实certificate,正如丹尼尔所说,大部分build议的替代DELETE / SELECT语句最终花费了大致相同的时间,就像灵魂装机提到的那样,这个语句几乎是我能够基于什么来构build的最好的我需要这样做 一旦我提供了另一个表C的索引,我的DELETE很快。

事后
这个练习中吸取了两个教训。 首先,很明显,我没有充分利用EXPLAIN语句的能力来更好地理解我的SQL查询的影响。 这是一个新手的错误,所以我不打算自己打起精神来。 我会从这个错误中吸取教训。 其次,违规代码是“快速完成”的结果,导致此问题的devise/testing不足导致此问题不能及时显示。 如果我生成了几个相当大的testing数据集作为这个新function的testinginput,我不会浪费我的时间和你的。 我在数据库方面的testing缺乏我的应用程序方面的深度。 现在我有机会改善这一点。

参考:EXPLAIN声明

从InnoDB中删除数据是您可以请求的最昂贵的操作。 正如你已经发现查询本身不是问题 – 大多数都将被优化到相同的执行计划。

虽然可能很难理解为什么所有情况下的DELETE最慢,但是有一个相当简单的解释。 InnoDB是一个事务性存储引擎。 这意味着如果你的查询在中途被中止,那么所有的logging仍然会存在,就好像没有任何事情发生一样。 一旦完成,所有将在同一时刻消失。 DELETE期间,连接到服务器的其他客户端将看到logging,直到DELETE完成。

为了实现这一点,InnoDB使用了一种名为MVCC(多版本并发控制)的技术。 它的基本function是为每个连接提供整个数据库的快照视图,就像事务的第一个语句开始时一样。 为了实现这一点,InnoDB内部的每条logging都可以有多个值 – 每个快照都有一个值。 这也是为什么在InnoDB上COUNTing需要一些时间 – 这取决于你当时看到的快照状态。

对于您的DELETE事务,根据您的查询条件识别的每条logging都被标记为删除。 由于其他客户端可能同时访问数据,因此无法立即将其从表中删除,因为他们必须查看各自的快照以保证删除的primefaces性。

一旦所有logging都被标记为删除,交易成功提交。 即使这样,在DELETE事务之前,所有其他与快照值一起工作的事务也不能立即从实际数据页面中删除。

所以实际上你的3分钟真的不是那么慢,考虑到所有logging都必须修改,以便准备以交易安全的方式进行删除。 声明运行时,您可能会“听到”您的硬盘正在工作。 这是由访问所有行引起的。 为了提高性能,可以尝试增加服务器的InnoDB缓冲池大小,并在DELETE时限制对数据库的其他访问,从而减lessInnoDB必须维护的历史logging数量。 有了额外的内存,InnoDB可能能够将你的表(大部分)读入内存,并避免一些磁盘寻道时间。

你三分钟的时间似乎很慢。 我的猜测是ID列没有正确索引。 如果你可以提供你正在使用的确切的表定义将是有帮助的。

我创build了一个简单的Python脚本来生成testing数据,并针对相同的数据集运行了多个不同版本的删除查询。 这是我的表格定义:

 drop table if exists a; create table a (id bigint unsigned not null primary key, data varchar(255) not null) engine=InnoDB; drop table if exists b; create table b like a; 

然后我插入100k行到25k行到b(22.5k也在a中)。 以下是各种删除命令的结果。 顺便说一下,我放弃了重新填充表格。

 mysql> DELETE FROM a WHERE EXISTS (SELECT b.id FROM b WHERE a.id=b.id); Query OK, 22500 rows affected (1.14 sec) mysql> DELETE FROM a USING a LEFT JOIN b ON a.id=b.id WHERE b.id IS NOT NULL; Query OK, 22500 rows affected (0.81 sec) mysql> DELETE a FROM a INNER JOIN b on a.id=b.id; Query OK, 22500 rows affected (0.97 sec) mysql> DELETE QUICK a.* FROM a,b WHERE a.id=b.id; Query OK, 22500 rows affected (0.81 sec) 

所有的testing都运行在采用Ubuntu 8.10和MySQL 5.0的Intel Core2四核2.5GHz,2GB RAM上。 请注意,一个sql语句的执行仍然是单线程的。


更新:

我更新了我的testing,以使用其matmat的架构。 我稍微修改它通过删除自动增量(我正在生成合成数据)和字符集编码(不工作 – 没有挖掘到它)。

这是我的新表定义:

 drop table if exists a; drop table if exists b; drop table if exists c; create table c (id varchar(30) not null primary key) engine=InnoDB; create table a ( id bigint(20) unsigned not null primary key, c_id varchar(30) not null, h int(10) unsigned default null, i longtext, j bigint(20) not null, k bigint(20) default null, l varchar(45) not null, m int(10) unsigned default null, n varchar(20) default null, o bigint(20) not null, p tinyint(1) not null, key l_idx (l), key h_idx (h), key m_idx (m), key c_id_idx (id, c_id), key c_id_fk (c_id), constraint c_id_fk foreign key (c_id) references c(id) ) engine=InnoDB row_format=dynamic; create table b like a; 

然后我重新进行了相同的testing,每行100k行和行25k行(并在两次运行之间重新填充)。

 mysql> DELETE FROM a WHERE EXISTS (SELECT b.id FROM b WHERE a.id=b.id); Query OK, 22500 rows affected (11.90 sec) mysql> DELETE FROM a USING a LEFT JOIN b ON a.id=b.id WHERE b.id IS NOT NULL; Query OK, 22500 rows affected (11.48 sec) mysql> DELETE a FROM a INNER JOIN b on a.id=b.id; Query OK, 22500 rows affected (12.21 sec) mysql> DELETE QUICK a.* FROM a,b WHERE a.id=b.id; Query OK, 22500 rows affected (12.33 sec) 

正如你可以看到这比以前慢了很多,可能是由于多个索引。 然而,这离三分钟还差得远。

你可能要看的其他东西是将longtext字段移动到模式结尾。 我似乎记得,如果所有大小限制的字段是第一,文本,blob等在最后,mySQL性能会更好。

尝试这个:

 DELETE a FROM a INNER JOIN b on a.id = b.id 

使用子查询通常会比外部查询中的每条logging运行时慢。

当我需要使用超大型数据(这里是一个150000行的样本testing表)时,我总是这样做:

 drop table if exists employees_bak; create table employees_bak like employees; insert into employees_bak select * from employees where emp_no > 100000; rename table employees to employees_todelete; rename table employees_bak to employees; 

在这种情况下,SQL将50000行过滤到备份表中。 查询级联在5秒内在我的慢机器上执行。 您可以将插入replace为您自己的筛选器查询。

这是对大数据库执行批量删除的技巧!=)

你在'a'中的每一行在'b'上做你的子查询。

尝试:

 DELETE FROM a USING a LEFT JOIN b ON a.id = b.id WHERE b.id IS NOT NULL; 

试试这个:

 DELETE QUICK A.* FROM A,B WHERE A.ID=B.ID 

它比正常的查询要快得多。

请参阅语法: http : //dev.mysql.com/doc/refman/5.0/en/delete.html

 DELETE FROM a WHERE id IN (SELECT id FROM b) 

也许你应该在运行这样一个hibernate查询之前重build这些索引。 那么,你应该定期重build它们。

 REPAIR TABLE a QUICK; REPAIR TABLE b QUICK; 

然后运行上述任何查询(即)

 DELETE FROM a WHERE id IN (SELECT id FROM b) 

查询本身已经处于最佳状态,更新索引会使整个操作花费很长时间。 您可以在操作之前禁用该表上的按键 ,这会加快速度。 如果您不需要立即使用,您可以稍后再打开它们。

另一种方法是将deleted标志列添加到您的表中,并调整其他查询,以便将这个值考虑在内。 在mysql中最快的布尔types是CHAR(0) NULL (true ='',false = NULL)。 这将是一个快速的操作,你可以删除之后的值。

在sql语句中expression了同样的想法:

 ALTER TABLE a ADD COLUMN deleted CHAR(0) NULL DEFAULT NULL; -- The following query should be faster than the delete statement: UPDATE a INNER JOIN b SET a.deleted = ''; -- This is the catch, you need to alter the rest -- of your queries to take the new column into account: SELECT * FROM a WHERE deleted IS NULL; -- You can then issue the following queries in a cronjob -- to clean up the tables: DELETE FROM a WHERE deleted IS NOT NULL; 

如果这也不是你想要的,你可以看看mysql文档中关于删除语句速度的说法 。

我知道这个问题已经解决了,因为OP的索引遗漏,但我想提供这个额外的build议,这是对这个问题更通用的情况下有效。

我个人曾经处理过,不得不从另一个表中删除许多行,根据我的经验,最好做到以下几点,特别是如果你期望删除很多行。 这种技术最重要的将会提高复制从属滞后,因为每个单独的增变器查询运行的时间越长,滞后就越差(复制是单线程的)。

所以,这里是: 首先做一个SELECT,作为一个单独的查询 ,记住脚本/应用程序中返回的ID,然后继续批量删除(例如,一次50000行)。 这将实现以下内容:

  • 每个删除语句都不会locking表太久,从而不会让复制滞后失控 。 如果您依靠复制为您提供相对最新的数据,这一点尤其重要。 使用批处理的好处是,如果您发现每个DELETE查询仍然需要很长时间,则可以将其调整为较小而不触及任何数据库结构。
  • 使用单独的SELECT的另一个好处是SELECT本身可能需要很长时间才能运行 ,特别是如果由于某种原因无法使用最好的数据库索引。 如果SELECT是DELETE内部的,那么当整个语句迁移到从属节点时,必须重新进行SELECT操作,可能会滞后于从属节点,因为它必须重新进行长select操作。 奴隶滞后,再次遭受严重。 如果您使用单独的SELECT查询,则此问题消失,因为您传递的所有内容都是ID列表。

让我知道,如果我的逻辑有什么地方的错误。

有关复制滞后和打击方法的更多讨论,请参阅本文中的“ MySQL从属滞后(延迟)说明和7种方法”

PS要注意的一件事,当然是在SELECT完成和DELETE开始的时间之间进行编辑。 我将通过使用与您的应用程序相关的交易和/或逻辑来让您处理这些细节。

顺便说一句,在发布上面的博客后,来自Percona的Baron Schwartz引起了我的注意,他的maatkit已经有一个工具就是为了这个目的 – mk-archiver。 http://www.maatkit.org/doc/mk-archiver.html

这很可能是你工作的最佳工具。

显然,构buildDELETE操作基础的SELECT查询是相当快的,所以我认为外键约束或索引是你查询速度非常慢的原因。

尝试

 SET foreign_key_checks = 0; /* ... your query ... */ SET foreign_key_checks = 1; 

这将禁用对外键的检查。 不幸的是,你不能禁用(至less我不知道如何)的InnoDB表的关键更新。 有了MyISAM表,你可以做类似的事情

 ALTER TABLE a DISABLE KEYS /* ... your query ... */ ALTER TABLE a ENABLE KEYS 

我其实没有testing这些设置是否会影响查询的持续时间。 但值得一试。

使用terminal连接数据库并执行下面的命令,查看它们每个的结果时间,你会发现删除10,100,1000,10000,100000条logging的时间不是相乘的。

  DELETE FROM #{$table_name} WHERE id < 10; DELETE FROM #{$table_name} WHERE id < 100; DELETE FROM #{$table_name} WHERE id < 1000; DELETE FROM #{$table_name} WHERE id < 10000; DELETE FROM #{$table_name} WHERE id < 100000; 

删除10万条logging的时间不是删除10万条logging的10倍。 然后,除了find一个更快的删除logging的方法外,还有一些间接的方法。

1,我们可以将table_name重命名为table_name_bak,然后从table_name_bak中selectlogging到table_name。

2,删除10000条logging,可以删除1000条logging10次。 有一个例子ruby脚本来做到这一点。

 #!/usr/bin/env ruby require 'mysql2' $client = Mysql2::Client.new( :as => :array, :host => '10.0.0.250', :username => 'mysql', :password => '123456', :database => 'test' ) $ids = (1..1000000).to_a $table_name = "test" until $ids.empty? ids = $ids.shift(1000).join(", ") puts "delete ==================" $client.query(" DELETE FROM #{$table_name} WHERE id IN ( #{ids} ) ") end 

通过id字段删除多个行在单个表中删除MySQL的基本技巧

DELETE FROM tbl_name WHERE id <= 100 AND id >=200; 该查询负责从某个表中删除100和200之间的匹配条件