在数据库方面,我是一个相对新手.我们正在使用MySQL,而我正在尝试加速似乎需要一段时间才能运行的SQL语句.我在SO上寻找类似的问题,但没找到.
目标是删除表A中表B中具有匹配id的所有行.
我目前正在做以下事情:
DELETE FROM a WHERE EXISTS (SELECT b.id FROM b WHERE b.id = a.id);
表a中约有100K行,表b中约有22K行.列'id'是两个表的PK.
这个声明在我的测试盒上运行大约需要3分钟 - 奔腾D,XP SP3,2GB内存,MySQL 5.0.67.这对我来说似乎很慢.也许不是,但我希望加快速度.是否有更好/更快的方法来实现这一目标?
编辑:
一些可能有用的其他信息.表A和B具有与我创建表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,尽管它只包含列id
和h
.
此外,分析结果如下:
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列上创建了一个INDEX并重新启动了EXPLAIN,我得到了:
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报告的时间一致.这是一个非常微妙的错误,因为鞋子在最后一分钟有一些额外的功能.事实证明,正如丹尼尔所说,大多数建议的备选DELETE/SELECT语句最终花费了相同的时间,并且正如soulmerge所提到的那样,该语句几乎是我能够基于什么构建的最好的语句.我需要这样做.一旦我为这个其他表C提供了一个索引,我的DELETE就很快了.
尸检:
从这项练习中汲取了两个经验教训.首先,很明显我没有利用EXPLAIN语句的强大功能来更好地了解我的SQL查询的影响.这是一个新手的错误,所以我不会打败自己.我会从那个错误中吸取教训.其次,违规代码是"快速完成"心态的结果,不充分的设计/测试导致这个问题没有尽快出现.如果我生成了几个相当大的测试数据集作为这个新功能的测试输入,我就没有浪费我的时间和你的时间.我在数据库方面的测试缺乏应用程序方面的深度.现在我有机会改善这一点.
参考:EXPLAIN声明
从InnoDB中删除数据是您可以请求的最昂贵的操作.正如您已经发现查询本身不是问题 - 无论如何,它们中的大多数都将针对相同的执行计划进行优化.
虽然可能很难理解为什么所有案例的DELETE都是最慢的,但有一个相当简单的解释.InnoDB是一个事务存储引擎.这意味着如果您的查询在中途中止,则所有记录仍然就位,就好像什么都没发生一样.一旦完成,所有都将在同一时刻消失.在DELETE期间,连接到服务器的其他客户端将看到记录,直到DELETE完成.
为此,InnoDB使用了一种名为MVCC(多版本并发控制)的技术.它基本上做的是为每个连接提供整个数据库的快照视图,就像第一个事务语句启动时一样.为此,InnoDB内部的每条记录都可以有多个值 - 每个快照一个.这也是InnoDB上COUNTing需要一些时间的原因 - 它取决于您当时看到的快照状态.
对于DELETE事务,根据您的查询条件识别的每条记录都会被标记为删除.由于其他客户端可能同时访问数据,因此无法立即从表中删除它们,因为它们必须查看各自的快照以保证删除的原子性.
一旦所有记录都被标记为删除,事务就会成功提交.即便如此,在DELETE事务之前使用快照值的所有其他事务也已结束之前,它们不能立即从实际数据页中删除.
所以事实上你的3分钟并不是那么慢,考虑到所有记录都必须被修改以便以交易安全的方式准备它们.可能你会在语句运行时"听到"你的硬盘工作.这是由访问所有行引起的.为了提高性能,您可以尝试增加服务器的InnoDB缓冲池大小,并尝试在DELETE时限制对数据库的其他访问,从而减少InnoDB每条记录必须维护的历史版本的数量.有了额外的内存,InnoDB可能能够将您的表(大部分)读入内存并避免一些磁盘寻找时间.
你三分钟的时间似乎很慢.我的猜测是id列没有被正确编入索引.如果您可以提供您正在使用的确切表格定义,那将会有所帮助.
我创建了一个简单的python脚本来生成测试数据,并针对同一数据集运行了多个不同版本的删除查询.这是我的表定义:
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行插入a和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)
所有测试均在Intel Core2四核2.5GHz,2GB RAM和Ubuntu 8.10以及MySQL 5.0上运行.注意,一个sql语句的执行仍然是单线程的.
更新:
我更新了我的测试以使用itsmatt的架构.我通过删除自动增量(我正在生成合成数据)和字符集编码(没有工作 - 没有挖掘它)稍微修改它.
这是我的新表定义:
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;
然后我重新进行相同的测试,在a中有10万行,在b中有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字段移动到架构的末尾.我似乎记得,如果所有大小限制的字段都是第一个并且text,blob等在最后,mySQL的表现会更好.
试试这个:
DELETE a FROM a INNER JOIN b on a.id = b.id
使用子查询往往比连接更慢,因为它们是为外部查询中的每个记录运行的.
当我必须处理超大数据(这里是一个具有150000行的示例测试表)时,这就是我经常做的事情:
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秒内在我运行缓慢的计算机上执行。您可以通过自己的过滤器查询将插入替换为select。
这是在大型数据库上执行批量删除的技巧!; =)