【问题标题】:Is this a good solution to remove duplicate MySQL rows?这是删除重复 MySQL 行的好方法吗?
【发布时间】:2011-03-19 04:30:30
【问题描述】:

我看到了创建具有唯一行的备用临时 MySQL 表的解决方案,但我不喜欢这个想法,因为我的表非常大,移动它们会很麻烦(如果有的话会产生巨大的问题在移动过程中出现错误)。

但是,我确实找到了以下内容。您对此有何看法(要检查的重复项是“field_name”)?

DELETE FROM table1
USING table1, table1 as vtable
WHERE (NOT table1.ID=vtable.ID)
AND (table1.field_name=vtable.field_name)

有人说这应该可行,但我不太确定。你怎么看?另外,是否有索引会改变这个命令的性能,比如在“field_name”上有一个索引?

编辑:在运行之前有什么方法可以测试查询吗?据我所知,MySQL 不支持对 DELETE 查询进行“解释”。

【问题讨论】:

  • 我正要发送另一个示例查询,但是,您测试了吗?在我看来,这两条记录都会被删除。
  • 您可以将“delete”替换为“select”来测试您的查询。

标签: mysql duplicates sql-delete


【解决方案1】:

请注意,您显示的查询将删除 两个 重复项。我假设您想保留其中一个。

以下是我将如何编写此查询:

DELETE t1 FROM table1 AS t1 JOIN table1 AS t2 
  ON t1.id > t2.id AND t1.field_name = t2.field_name;

通过使用大于而不是不等于,您只删除一行(后一行),而不是同时删除。

(id, field_name) 上的复合索引可能会有所帮助。您应该通过 MySQL 的EXPLAIN 确认这一点以获取优化报告。但是EXPLAIN 只支持SELECT 查询,所以你应该运行一个等效的SELECT 来确认优化:

EXPLAIN SELECT * FROM table1 AS t1 JOIN table1 AS t2 
  ON t1.id > t2.id AND t1.field_name = t2.field_name;

您还询问了有关测试的问题。我建议将包含重复行的样本复制到 test 数据库中的表中:

CREATE TABLE test.table1test SELECT * FROM realdb.table1 LIMIT 10000;

现在您可以对样本数据进行实验,直到您对 DELETE 解决方案的正确性感到满意为止。

USE test;
SET autocommit = 0;
DELETE ... 
ROLLBACK;

我建议将test 数据库中的临时表命名为与真实数据库中的真实表不同的名称。以防万一您在运行实验性DELETE 时意外仍在使用真实数据库作为默认数据库!


你的cmets:

USE test 是一个 mysql 客户端内置命令。它将test 数据库设置为默认数据库。当您在查询中命名表而不用数据库名称限定它们时,这将是默认数据库。见http://dev.mysql.com/doc/refman/5.1/en/use.html

SET autocommit = 0 隐式关闭为每个查询提交事务的默认行为。所以你必须明确给出COMMITROLLBACK 命令来完成一个事务。见http://dev.mysql.com/doc/refman/5.1/en/commit.html

在试验时使用ROLLBACK 是值得的,因为它会丢弃在该事务中所做的更改。这是一种快速返回测试数据初始状态的方法,因此您可以尝试另一个实验。

DELETE t1 不是错字。 DELETE 删除行,而不是整个表。 t1 是每个满足语句条件的 row 的别名(尽管条件可能包括表中的每一行)。多表删除的描述见http://dev.mysql.com/doc/refman/5.1/en/delete.html

有点像当你在 PHP 中运行一个循环并使用一个变量来迭代循环时:for ($i=0; $i<100; ++$i) ... 变量$i 接受一系列值,每次循环它都有不同的值。

这是一个演示,展示了我的解决方案如何删除多个重复项。我在我的test 数据库中运行了这个,我直接从我的命令窗口粘贴了结果:

mysql> create table table1 (id serial primary key, field_name varchar(10));
Query OK, 0 rows affected (0.45 sec)

mysql> insert into table1 (field_name) 
       values (42), (42), (42), (42), (42), (42);
Query OK, 6 rows affected (0.00 sec)
Records: 6  Duplicates: 0  Warnings: 0

mysql> select * from table1;
+----+------------+
| id | field_name |
+----+------------+
|  1 | 42         | 
|  2 | 42         | 
|  3 | 42         | 
|  4 | 42         | 
|  5 | 42         | 
|  6 | 42         | 
+----+------------+
6 rows in set (0.00 sec)

mysql> delete t1 from table1 t1 join table1 t2 
       on t1.id > t2.id and t1.field_name = t2.field_name;
Query OK, 5 rows affected (0.00 sec)

mysql> select * from table1;
+----+------------+
| id | field_name |
+----+------------+
|  1 | 42         | 
+----+------------+
1 row in set (0.00 sec)

【讨论】:

  • 感谢比尔的帮助!你的答案得到了赞成,所以我假设你的答案是最好的解决方案(不是说其他​​人不好)。请您解释一下以“USE test; SET ...”开头的代码块是什么意思?另外,为了确保大于号的使用将确保所有重复项都将被删除,即使有超过 1 个重复项(例如 5 个)?非常感谢。
  • 还忘了问:您的第一个解决方案代码块不是拼写错误,对吗?你把“删除t1”。这是否意味着它会删除整个表或其他东西?抱歉所有问题,这对我来说有点复杂=)
  • 只是想停下来再次感谢您对这篇精彩的文章。两年多后我仍然提到它!
【解决方案2】:

该查询应该有效。拥有索引会改变性能,但这实际上取决于表的大小。

至于测试这一点,我会将数据的子集复制到临时表中,然后在临时表上运行命令,然后再在真实表上运行。

请记住在执行任何主要批处理作业之前始终备份表,以便您可以随时回滚。

【讨论】:

    【解决方案3】:

    我使用的方法避免了JOIN 条件并且应该明显更快:

    DELETE FROM table1 WHERE id NOT IN (SELECT MIN(x.id) FROM table1 AS x GROUP BY x.field_name);
    

    子选择收集您要保留的 id 列表。这将允许您为每个 field_name 保留一个唯一的行。然后DELETE 语句将删除所有多余的重复行。

    另外,是的,field_name 字段上的索引将提高您的查询性能。

    【讨论】:

    • 在 MySQL 中几乎任何GROUP BY 的使用都会调用一个临时表,这会严重影响性能。
    • @Bill - 刚刚对我的一个测试数据库进行了快速测试。当然,只有 30,000 行,但在我使用它的情况下,GROUP BY 的性能明显优于 JOIN
    • 嗯,很好。我希望它也适用于 OP 的数据库。
    猜你喜欢
    • 2011-05-06
    • 2015-07-23
    • 2011-04-08
    • 1970-01-01
    • 1970-01-01
    • 2017-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多