对于数据库,我是一个相对新手。我们正在使用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语句,如 Daniel 所述,最终花费了相同的时间,并且 soulmerge 提到,声明很漂亮我能够根据自己的需要做出最好的建设。一旦我为这个其他表C提供了一个索引,我的DELETE就很快了。
尸检:
从这项练习中汲取了两个经验教训。首先,很明显我没有利用EXPLAIN语句的强大功能来更好地了解我的SQL查询的影响。这是一个新手的错误,所以我不会打败自己。我会从那个错误中吸取教训。其次,违规代码是“快速完成”心态的结果,不充分的设计/测试导致这个问题没有尽快出现。如果我生成了几个相当大的测试数据集作为这个新功能的测试输入,我就没有浪费我的时间和你的时间。我在数据库方面的测试缺乏应用程序方面的深度。现在我有机会改善这一点。
答案 0 :(得分:74)
从InnoDB中删除数据是您可以请求的最昂贵的操作。正如您已经发现查询本身不是问题 - 无论如何,它们中的大多数都将针对相同的执行计划进行优化。
虽然可能很难理解为什么所有案例的DELETE都是最慢的,但有一个相当简单的解释。 InnoDB是一个事务存储引擎。这意味着如果您的查询在中途中止,则所有记录仍然就位,就好像什么都没发生一样。一旦完成,所有都将在同一时刻消失。在DELETE期间,连接到服务器的其他客户端将看到记录,直到DELETE完成。
为实现这一目标,InnoDB使用了一种称为MVCC(多版本并发控制)的技术。它基本上做的是为每个连接提供整个数据库的快照视图,就像第一个事务语句启动时一样。为实现这一目标,InnoDB内部的每条记录都可以有多个值 - 每个快照一个。这也是InnoDB上COUNTing需要一些时间的原因 - 这取决于您当时看到的快照状态。
对于您的DELETE事务,根据您的查询条件识别的每条记录都会被标记为删除。由于其他客户端可能同时访问数据,因此无法立即从表中删除它们,因为他们必须查看各自的快照以保证删除的原子性。
一旦所有记录都被标记为删除,交易就会成功提交。即便如此,在DELETE事务之前使用快照值的所有其他事务也已结束之前,它们不能立即从实际数据页中删除。
所以实际上你的3分钟并不是那么慢,考虑到所有记录都必须被修改以便以交易安全的方式准备它们。可能你会在语句运行时“听到”你的硬盘工作。这是由访问所有行引起的。 为了提高性能,您可以尝试增加服务器的InnoDB缓冲池大小,并尝试在DELETE时限制对数据库的其他访问,从而减少InnoDB每条记录必须维护的历史版本的数量。 有了额外的内存,InnoDB可能能够将您的表(大部分)读入内存并避免一些磁盘寻找时间。
答案 1 :(得分:9)
你的三分钟时间似乎很慢。我的猜测是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语句的执行仍然是单线程的。
更新
我更新了我的测试以使用其匹配的架构。我通过删除自动增量(我正在生成合成数据)和字符集编码(没有工作 - 没有深入研究它)稍微修改它。
这是我的新表定义:
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的表现会更好。
答案 2 :(得分:8)
试试这个:
DELETE a
FROM a
INNER JOIN b
on a.id = b.id
使用子查询往往比连接更慢,因为它们是为外部查询中的每个记录运行的。
答案 3 :(得分:5)
这是我经常做的,当我必须使用超大数据(这里:一个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。
这是在大型数据库上执行批量删除的技巧!; =)
答案 4 :(得分:3)
你在'a'的每一行都在'b'上做你的子查询。
尝试:
DELETE FROM a USING a LEFT JOIN b ON a.id = b.id WHERE b.id IS NOT NULL;
答案 5 :(得分:3)
试试这个:
DELETE QUICK A.* FROM A,B WHERE A.ID=B.ID
它比普通查询快得多。
答案 6 :(得分:3)
我知道由于OP的索引遗漏,这个问题已经解决了,但我想提供这个额外的建议,这对于这个问题的更通用的情况是有效的。
我个人已经处理过必须从另一个表中删除许多行,根据我的经验,最好执行以下操作,特别是如果您希望删除大量行。这项技术最重要的是将改善复制从属延迟,因为每个单一的mutator查询运行的时间越长,滞后就越严重(复制是单线程的)。
所以,这里是:首先执行SELECT,作为单独的查询,记住脚本/应用程序中返回的ID,然后继续批量删除(例如,一次删除50,000行) )。 这将实现以下目标:
让我知道我的逻辑是否存在错误。
有关复制延迟及其对抗方式的更多讨论,与此类似,请参阅MySQL Slave Lag (Delay) Explained And 7 Ways To Battle It
P.S。当然,要注意的一件事是在SELECT完成和DELETE开始之间对表进行潜在的编辑。我将通过使用与您的应用程序相关的事务和/或逻辑来处理这些细节。
答案 7 :(得分:2)
DELETE FROM a WHERE id IN (SELECT id FROM b)
答案 8 :(得分:2)
也许你应该在运行这样一个查询之前重建指标。好吧,你应该定期重建它们。
REPAIR TABLE a QUICK;
REPAIR TABLE b QUICK;
然后运行上述任何查询(即)
DELETE FROM a WHERE id IN (SELECT id FROM b)
答案 9 :(得分:2)
查询本身已经处于最佳状态,更新索引会导致整个操作花费那么长时间。在操作之前,您可以在该表上disable the keys ,这应该加快速度。如果您不立即需要它们,可以稍后重新打开它们。
另一种方法是在表中添加deleted
标志列并调整其他查询,以便将该值考虑在内。 mysql中最快的布尔类型是CHAR(0) NULL
(true ='',false = NULL)。这将是一个快速操作,您可以删除之后的值。
在sql语句中表达的相同想法:
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文档对speed of delete statements的看法。
答案 10 :(得分:2)
这很可能是你工作的最佳工具。
答案 11 :(得分:1)
显然构建SELECT
操作基础的DELETE
查询非常快,所以我认为外键约束或索引是查询速度极慢的原因。< / p>
尝试
SET foreign_key_checks = 0;
/* ... your query ... */
SET foreign_key_checks = 1;
这将禁用对外键的检查。不幸的是,您无法禁用(至少我不知道如何)使用InnoDB表进行密钥更新。使用MyISAM表,您可以执行类似
的操作ALTER TABLE a DISABLE KEYS
/* ... your query ... */
ALTER TABLE a ENABLE KEYS
我实际上没有测试这些设置是否会影响查询持续时间。但值得一试。
答案 12 :(得分:1)
使用终端连接数据库并执行下面的命令,查看每个数据的结果时间,你会发现删除10,100,1000,10000,100000记录的次数不会被乘以。
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;
删除1万条记录的时间不是删除10万条记录的10倍。 然后,除了找到更快删除记录的方法外,还有一些间接方法。
1,我们可以将table_name重命名为table_name_bak,然后从table_name_bak到table_name选择记录。
2,要删除10000条记录,我们可以删除1000条记录10次。有一个示例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
答案 13 :(得分:-2)
通过id字段
在单个表中删除多个Row形式MySQL的基本技巧 DELETE FROM tbl_name WHERE id <= 100 AND id >=200;
此查询负责从特定表中删除100到200之间的匹配条件