在数据库方面我是一个相对新手。我们正在使用 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 分钟 - Pentium D、XP SP3、2GB 内存、MySQL 5.0.67。这对我来说似乎很慢。也许不是,但我希望加快速度。有没有更好/更快的方法来实现这一点?


编辑:

一些可能有帮助的附加信息。表 A 和 B 具有与我执行以下操作来创建表 B 相同的结构:

CREATE TABLE b LIKE a;

表 a(以及表 b)有一些索引来帮助加快对其进行的查询。再说一遍,我在数据库工作方面相对新手,并且仍在学习中。我不知道这对事情有多大影响(如果有的话)。我认为它确实有影响,因为索引也必须清理,对吧?我还想知道是否有任何其他数据库设置可能会影响速度。

另外,我正在使用 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 类似,但它只包含列 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

解决了

感谢所有的回复和评论。他们确实让我思考这个问题。感谢 多乔 让我通过问一个简单的问题“是否有其他表引用 a.id?”来摆脱这个问题。

问题是表 A 上有一个 DELETE TRIGGER,它调用存储过程来更新另外两个表 C 和 D。表 C 有一个返回 a.id 的 FK,在存储过程中执行了一些与该 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 和我的删除时间与报告的时间一致 马特坎普. 。这对我来说是一个非常微妙的错误,因为我在最后一刻硬塞了一些附加功能。事实证明,大多数建议的替代 DELETE/SELECT 语句,如 丹尼尔 说,最终花费了基本上相同的时间和 灵魂融合 如上所述,该声明几乎是我能够根据我需要做的事情构建的最好的声明。一旦我为另一个表 C 提供了索引,我的删除速度就会很快。

事后剖析:
从这次练习中吸取了两个教训。首先,很明显,我没有利用 EXPLAIN 语句的强大功能来更好地了解 SQL 查询的影响。这是一个菜鸟错误,所以我不会因为这个错误而责备自己。我会从那个错误中吸取教训。其次,有问题的代码是“快速完成”心态的结果,不充分的设计/测试导致这个问题没有更快地出现。如果我生成了几个相当大的测试数据集来用作此新功能的测试输入,我就不会浪费我的时间,也不会浪费你的时间。我在数据库端的测试缺乏应用程序端的深度。现在我有机会改进这一点。

参考:解释声明

有帮助吗?

解决方案

从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语句的执行仍然是单线程的。


更新

我更新了我的测试以使用其匹配的架构。我通过删除自动增量(我正在生成合成数据)和字符集编码(没有工作 - 没有深入研究它)稍微修改它。

这是我的新表定义:

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。

这是在大型数据库上执行批量删除的技巧!; =)

你在'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

我知道由于OP的索引遗漏,这个问题已经得到了很好的解决,但我想提供这个额外的建议,这对于这个问题的更通用的情况是有效的。

我个人曾经处理过必须从一个表中删除另一个表中存在的许多行的问题,根据我的经验,最好执行以下操作,特别是如果您希望删除大量行。最重要的是,该技术将改善复制从属延迟,因为每个单个变异器查询运行的时间越长,延迟就越严重(复制是单线程的)。

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

  • 每一条删除语句都不会锁定表太久,从而不会让复制滞后失控. 。如果您依赖复制来为您提供相对最新的数据,这一点尤其重要。使用批处理的好处是,如果您发现每个 DELETE 查询仍然花费太长的时间,您可以将其调整得更小,而无需触及任何 DB 结构。
  • 使用单独的 SELECT 的另一个好处是 SELECT 本身可能需要很长时间才能运行, ,特别是如果它出于某种原因无法使用最好的数据库索引。如果 SELECT 位于 DELETE 的内部,则当整个语句迁移到从属设备时,它将必须重新执行 SELECT,这可能会滞后从属设备,因为它必须重新执行长选择。奴隶滞后再次遭受严重打击。如果您使用单独的 SELECT 查询,这个问题就会消失,因为您传递的只是一个 ID 列表。

如果我的逻辑有问题,请告诉我。

有关复制滞后及其解决方法的更多讨论(与此类似),请参阅 MySQL Slave Lag(延迟)解释以及 7 种解决方法

附:当然,需要注意的一件事是,在 SELECT 完成和 DELETE 开始之间可能会对表进行编辑。我将让您通过使用与您的应用程序相关的事务和/或逻辑来处理这些细节。

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

也许你应该在运行这样一个hugh查询之前重建索引。好吧,你应该定期重建它们。

REPAIR TABLE a QUICK;
REPAIR TABLE b QUICK;

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

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

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

另一种方法是在表中添加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文档对删除语句的速度

顺便说一下,在我的博客上发布以上内容后,来自Percona的 Baron Schwartz 引起了我的注意,他的 maatkit 已经有了一个专门用于此目的的工具 - mk-archiver。 http://www.maatkit.org/doc/mk-archiver.html

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

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

尝试

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 

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

使用终端连接数据库并执行下面的命令,查看每个数据的结果时间,你会发现删除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

通过id字段

在单个表中删除多个Row形式MySQL的基本技巧

DELETE FROM tbl_name WHERE id <= 100 AND id >=200; 此查询负责从特定表中删除100到200之间的匹配条件

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top