【问题标题】:MySQL remove duplicates from big database quickMySQL快速从大数据库中删除重复项
【发布时间】:2009-10-30 20:01:24
【问题描述】:

我的大型(>Mil 行)MySQL 数据库被重复项弄乱了。我认为它可能是填充它们的整个数据库的 1/4 到 1/2。 我需要快速摆脱它们(我的意思是查询执行时间)。 下面是它的外观:
id(索引)|文本1 |文本2 | 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 分钟)

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

【问题讨论】:

  • 请说明:a) 是否需要重新编号 id 字段? b) 我们期望重复的数量或比例是多少? (有助于决定就地工作或创建新表)c)当前表上存在哪些索引。
  • a) 不需要重新编号 id 字段 b) 我的估计:从 1/4 到 1/2 的 db 是重复的 c) id 是唯一的索引。我会相应地编辑问题。

标签: sql mysql duplicates


【解决方案1】:

我相信这会做到,使用重复键 + 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;

应该比任何需要 group by 或 distinct 或子查询,甚至 order by 的东西都要快得多。这甚至不需要文件排序,这会降低大型临时表的性能。仍然需要对原始表进行全面扫描,但无法避免。

【讨论】:

  • 谢谢,它有效! 120 万行在 60 分钟内变成了 60 万行,因此每分钟写入大约 10000 行。也感谢您的清晰解释! :)
  • 这是一个很大的帮助。谢谢
  • @ʞɔıu (upsideDownNick) 简单有效。对于那些不关心 text3 not null 部分的人,您可以使用 INSERT IGNORE (不考虑 ON DUPLICATE UPDATE 部分),mysql 将忽略错误并仅插入它找到的第一个不同值(忽略后续重复项)。
  • +1 这是一个聪明的解决方案。在我的情况下,客户端在大约 10 分钟内失去与服务器的连接(表有 45+ 百万条记录)并导致混乱的锁被打开等 - 关于如何处理这个问题的任何建议?
  • 如果有人感兴趣,我在stackoverflow.com/questions/3311903/…上使用更多用例扩展了@ʞɔıu 回复
【解决方案2】:

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

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

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

【讨论】:

  • 看起来 MySQL 错误阻止了您的查询(尤其是 IGNORE 部分)工作:错误代码:1062 键 'text1' 的重复条目 'abc-def'
  • @bizzz 如果遇到该错误,您只需运行set session old_alter_table=1,然后再试一次。
  • 这不适用于 BLOB/TEXT 列。它给出了错误“在没有密钥长度的密钥规范中使用的 BLOB/TEXT 列‘名称’”
【解决方案3】:
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 排序(意味着我们选择第一个非空 text3 记录)。然后我们从该结果中选择 id(这些是好的 id...它们不会被删除)并删除所有不是这些的 ID。

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

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

如果您想走“创建新表并替换旧表”的路线。您可以使用非常内部的 select 语句来创建您的 insert 语句。

特定于 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 仍然存在)
【解决方案4】:

删除重复项而不删除外键

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);

【讨论】:

  • 这应该是正确的答案。简单易操作。
【解决方案5】:

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

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

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

【讨论】:

  • 它会将空值放在最后,但它不满足“保留第一个在 text3 中没有空值的”的请求。为此,您需要按 ID ASC 排序,并在您的语句中添加 WHERE text3 IS NOT NULL。
  • 这是一个很好的观点。然而,该要求与他的样本输出相矛盾:2 |啊! bbb | NULL 也许他会告诉我们他真正想要什么。
  • 我重读了他的请求。看来他不在乎,只要有一个非空值,就保留非空值。所以你的例子很适合。 :)
  • 谢谢,工作。 120 万行需要将近 3 个小时;估计每分钟写入 4000 行。它留下了最大的 text3 字段的重复项,这与我的数据库逻辑相对应。
【解决方案6】:

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

举个例子here

【讨论】:

    【解决方案7】:

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

    从 my_tbl 中删除 身份证在哪里( 选择编号 从(选择 id,row_number() over (partition by text1, text2 order by text3 desc) as rn 来自 my_tbl /* 可选:其中 text1 像 'a%' */ ) 作为 t2 其中 rn > 1 )

    可选的 where 子句意味着您必须多次运行它,每个字母运行一次,等等。在 text1 上创建索引?

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

    【讨论】:

    • 抱歉,错误代码 : 1064 near '(partition by...'
    • 我猜MySql没有解析函数。我稍后再试。
    • 你能运行:create table dups as SELECT text1, text2, max(case when text3 is null then 1 else 0) as has_null3, max(case when text3 is not null then 1 else 0) as has_not_null3 , min(case when text3 is not null then id else null) as pref_id FROM my_tbl GROUP BY text1, text2 have count(*) > 1 这将为我们提供重复的 text1/2 列表和一些“首选”身份证。如果花费的时间太长,而且可能会,请添加“where text1 like 'a%'”或类似的内容。
    【解决方案8】:

    我知道这是一个旧线程,但我有一个有点混乱的方法,它更快且可自定义,就速度而言,我会说 10 秒而不是 100 秒 (10:1)。

    我的方法确实需要您试图避免的所有 混乱 东西:

    • 按(和拥有)分组
    • 使用 ORDER BY 分组连接
    • 2 个临时表
    • 使用磁盘上的文件!
    • 不知何故(php?)删除文件后

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

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

    编辑:如果我得到 cmets,我会进一步解释它是如何工作的 :)

    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;
    

    【讨论】:

      【解决方案9】:

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

       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
      

      【讨论】:

        猜你喜欢
        • 2017-07-11
        • 1970-01-01
        • 2022-01-10
        • 2015-12-29
        • 1970-01-01
        • 1970-01-01
        • 2018-11-20
        • 2013-10-07
        • 2019-07-17
        相关资源
        最近更新 更多