MySQL快速从大型数据库中删除重复项


70

我有大的(>百万行)MySQL数据库被重复弄乱了。我认为这可能是充满它们的整个数据库的1/4到1/2。我需要快速摆脱它们(我是指查询执行时间)。外观如下:
id(索引)| text1 | text2 | text3
text1&text2组合应该是唯一的,如果有重复项,则仅应保留一个text3 NOT NULL组合。例:

1 | abc | def | NULL  
2 | abc | def | ghi  
3 | abc | def | jkl  
4 | aaa | bbb | NULL  
5 | aaa | bbb | NULL  

...成为:

1 | abc | def | ghi   #(doesn't realy matter id:2 or id:3 survives)   
2 | aaa | bbb | NULL  #(if there's no NOT NULL text3, NULL will do)

新的id可以是任何东西,它们不依赖于旧表的id。
我已经尝试过类似的事情:

CREATE TABLE tmp SELECT text1, text2, text3
FROM my_tbl;
GROUP BY text1, text2;
DROP TABLE my_tbl;
ALTER TABLE tmp RENAME TO my_tbl;

或SELECT DISTINCT和其他变体。
当它们在小型数据库上工作时,我的查询执行时间却非常长(实际上从未到尽头;> 20分钟)

有没有更快的方法可以做到这一点?请帮我解决这个问题。


2
请指定:a)是否需要重新编号id字段?b)我们期望重复的数量或比例是多少?(用于决定就地工作或创建新表)c)当前表上存在哪些索引。
09年

a)不必对id字段重新编号b)我的估计:从db的1/4到1/2是重复的c)id是唯一的索引。我将相应地编辑问题。
bizzz

Answers:


148

我相信使用重复键+ ifnull()可以做到这一点:

create table tmp like yourtable;

alter table tmp add unique (text1, text2);

insert into tmp select * from yourtable 
    on duplicate key update text3=ifnull(text3, values(text3));

rename table yourtable to deleteme, tmp to yourtable;

drop table deleteme;

应该比需要分组依据或不重复,子查询甚至排序依据的速度快得多。这甚至不需要文件排序,这将破坏大型临时表的性能。仍然需要对原始表进行全面扫描,但这是不可避免的。


谢谢,它有效!120万行在60分钟内变为60万行,因此每分钟写入10000行。也感谢您的明确解释!:)
bizzz

这是一个很大的帮助。谢谢
rpearce 2012年

15
@ʞɔıu(upsideDownNick)简单有效。对于那些不关心text3不是null部分的用户,可以使用INSERT IGNORE(不考虑ON DUPLICATE UPDATE部分),并且mysql将忽略错误并仅插入它找到的第一个不同的值(忽略随后的重复项)。
Tony gil 2012年

+1这是一个聪明的解决方案。以我为例,客户端在大约10分钟内(与表有45+百万条记录)失去与服务器的连接,并导致混乱的锁被打开,等等。关于如何处理此问题,有什么建议吗?
马特

如果有人有兴趣,我扩大了与进一步用例@ʞɔıu响应stackoverflow.com/questions/3311903/...
塞萨尔还原-Gomar

95

找到了这个简单的1行代码即可完全满足我的需要:

ALTER IGNORE TABLE dupTest ADD UNIQUE INDEX(a,b);

取自:http : //mediakey.dk/~cc/mysql-remove-duplicate-entries/


7
看起来像MySQL错误,导致您的查询(尤其是IGNORE部分查询)无法正常工作:错误代码:1062键“ text1”的条目“ abc-def”重复
bizzz 2011年

12
@bizzzset session old_alter_table=1如果出现该错误,您只需要运行,然后重试。
马修(Matthew),

这不适用于BLOB / TEXT列。它给出了错误“密钥规范中使用的BLOB / TEXT列'name',没有密钥长度”
Dashrath

12
DELETE FROM dups
WHERE id NOT IN(
    SELECT id FROM (
        SELECT DISTINCT id, text1, text2
            FROM dups
        GROUP BY text1, text2
        ORDER BY text3 DESC
    ) as tmp
)

这将查询所有记录,按区别字段分组和按ID排序(意味着我们选择第一个非null的text3记录)。然后,我们从结果中选择ID(这些ID很好,它们将不会被删除),然后删除所有不包含这些ID的ID。

像这样影响整个表的任何查询都会很慢。您只需要运行它并让其推出,以便将来防止它。

完成此“修复”之后,我将对该表应用UNIQUE INDEX(text1,text2)。为了防止将来重复的可能性。

如果要执行“创建新表并替换旧表”路线。您可以使用非常内部的select语句来创建您的插入语句。

特定于MySQL(假设新表名为my_tbl2,并且具有完全相同的结构):

INSERT INTO my_tbl2
    SELECT DISTINCT id, text1, text2, text3
            FROM dups
        GROUP BY text1, text2
        ORDER BY text3 DESC

有关更多信息,请参见MySQL INSERT ... SELECT


抱歉,您的两个建议都删除了重复项,但没有选择正确的text3字段来生存(在没有NOT NULL替代项的情况下仍保留NULL)
bizzz 2009年

8

删除重复项而不删除外键

create table tmp like mytable;
ALTER TABLE tmp ADD UNIQUE INDEX(text1, text2, text3, text4, text5, text6);
insert IGNORE into tmp select * from mytable;
delete from mytable where id not in ( select id from tmp);

1
这应该是正确的答案。简单且可操作。
PKHunter 2014年

3

如果可以创建新表,请在text1 + text2字段上使用唯一键。然后插入表中忽略错误(使用INSERT IGNORE语法):

select * from my_tbl order by text3 desc
  • 我认为text3 desc的顺序会将NULL放在最后,但是请仔细检查。

所有这些列上的索引都会有很大帮助,但是现在创建它们可能会很慢。


它将空值放在最后,但不满足“保留第一个在text3中没有空值的请求”的请求。为此,您需要按ID ASC排序,并在您的语句中添加WHERE text3 IS NOT NULL。
凯文·佩诺

那是个很好的观点。但是,该要求与他的样本输出相矛盾:2 | aaa | bbb | 空也许他会告诉我们他真正想要的是什么。
Scott Saunders

我重读了他的要求。看起来他并不在乎,只要存在非null,就保留非null。因此,您的示例将非常适合。:)
凯文·佩诺

谢谢,工作。排成120万,花了将近3个小时;估计每分钟写入4000行。它留有最大的text3字段重复项,这与我的数据库逻辑相对应。
bizzz

1

对于很少重复的大型表,您可能要避免将整个表复制到另一个位置。一种方法是创建一个临时表,其中包含要保留的行(对于每个具有重复项的键),然后从原始表中删除重复项。

这里给出一个例子。


0

我对MySQL没有太多的经验。如果它具有分析功能,请尝试:

从my_tbl中删除
 在(
     选择编号 
       从(选择ID,row_number()
                            over(按text1划分,text2按text3 desc排序)为rn
               来自my_tbl
               / *可选:其中text1如'a%'* /
             )作为t2
       rn> 1
     )

可选的where子句使您必须多次运行它,每个字母一个,以此类推。在text1上创建索引?

在运行此命令之前,请确认“ text desc”将在MySQL中最后排空。


抱歉,错误代码:'(partition by ...')附近1064
bizzz,2009年

我猜MySql没有解析功能。我稍后再试。
redcayuga

您可以运行吗:创建表dups作为SELECT text1,text2,max(case text3为null时为1,否则为0,否则为0)as has_null3,max(case text3不为null则为1,否则为0,否则)为has_not_null3,min(case text3为不为null,然后为id,否则为null)as pref_id来自my_tbl GROUP BY text1,text2的count(*)> 1这将为我们提供重复的text1 / 2列表和一些“首选” ID。如果花费的时间太长,并且可能会,那么请添加“ where text1 like'a%'”或类似的内容。
redcayuga

0

我知道这是一个旧线程,但是我的方法有些混乱,它的速度和可定制性要快得多,就速度而言,我会说是10秒而不是100秒(10:1)。

我的方法确实需要您尝试避免的所有杂物

  • 分组依据(和具有)
  • 与ORDER BY的群组连拍
  • 2个临时表
  • 使用磁盘上的文件!
  • 以某种方式(php?)之后删除文件

但是,当您谈论百万美元(或者在我的情况下为数百万美元)时,这是值得的。

无论如何,这不是很多,因为评论是葡萄牙语,但这是我的示例:

编辑:如果我有任何评论,我将进一步解释其工作原理:)

START TRANSACTION;

DROP temporary table if exists to_delete;

CREATE temporary table to_delete as (
    SELECT
        -- escolhe todos os IDs duplicados menos os que ficam na BD
        -- A ordem de escolha dos IDs é dada por "ORDER BY campo_ordenacao DESC" em que o primeiro é o que fica
        right(
            group_concat(id ORDER BY campos_ordenacao DESC SEPARATOR ','),
            length(group_concat(id ORDER BY campos_ordenacao DESC SEPARATOR ',')) 
                - locate(",",group_concat(id ORDER BY campos_ordenacao DESC SEPARATOR ','))
        ) as ids,

        count(*) as c

    -- Tabela a eliminar duplicados
    FROM teste_dup

    -- campos a usar para identificar  duplicados
    group by test_campo1, test_campo2, teste_campoN
    having count(*) > 1 -- é duplicado
);

-- aumenta o limite desta variável de sistema para o máx 
SET SESSION group_concat_max_len=4294967295;

-- envia os ids todos a eliminar para um ficheiro
select group_concat(ids SEPARATOR ',') from to_delete INTO OUTFILE 'sql.dat';

DROP temporary table if exists del3;
create temporary table del3 as (select CAST(1 as signed) as ix LIMIT 0);

-- insere os ids a eliminar numa tabela temporaria a partir do ficheiro
load data infile 'sql.dat' INTO TABLE del3
LINES TERMINATED BY ',';

alter table del3 add index(ix);

-- elimina os ids seleccionados
DELETE teste_dup -- tabela 
from teste_dup -- tabela

join del3 on id=ix;

COMMIT;

0

您可以使用此简单查询删除所有重复的条目。这将选择所有重复的记录并将其删除。

 DELETE i1 
FROM TABLE i1
LEFT JOIN TABLE i2
  ON i1.id = i2.id
 AND i1.colo = i2.customer_invoice_id
 AND i1.id < i2.id
WHERE i2.customer_invoice_id IS NOT NULL
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.