web-dev-qa-db-ja.com

MySQLは大きなデータベースから重複をすばやく削除します

重複してめちゃくちゃになった(> Mil行)MySQLデータベースがあります。私はそれがそれらで満たされたデータベース全体の1/4から1/2になり得ると思います。それらをすばやく取り除く必要があります(つまり、クエリの実行時間を意味します)。外観は次のとおりです。
id(インデックス)| text1 | text2 | text3
text1とtext2の組み合わせは一意である必要があります。重複がある場合、text3 NOT NULLとの組み合わせは1つだけ残してください。例:

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分以上)

それを行うより速い方法はありますか?この問題の解決を手伝ってください。

69
bizzz

私はこれが重複キー+ 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、subquery、さらにはorder byを必要とするものよりもはるかに高速である必要があります。これには、ファイルの並べ替えさえ必要ありません。これは、大きな一時テーブルのパフォーマンスを低下させます。元のテーブルを完全にスキャンする必要がありますが、それを避けることはできません。

146
ʞɔıu

私が必要なことを正確に行うために、この単純な1行のコードを見つけました。

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

から取得: http://mediakey.dk/~cc/mysql-remove-duplicate-entries/

95
liorq
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を削除します。

テーブル全体に影響を与えるこのようなクエリは遅くなります。実行するだけでロールアウトできるので、将来はそれを防ぐことができます。

この「修正」を行った後、一意のインデックス(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 を参照してください。

12
Kevin Peno

外部キーを削除せずに重複を削除する

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

新しいテーブルを作成できる場合は、text1 + text2フィールドに一意のキーを使用して作成します。次に、エラーを無視してテーブルに挿入します(INSERT IGNORE構文を使用):

select * from my_tbl order by text3 desc
  • Text3 descによる順序はNULLを最後に置くと思いますが、それを再確認してください。

これらのすべての列のインデックスは大いに役立つ可能性がありますが、今ではインデックスの作成がかなり遅くなる可能性があります。

3
Scott Saunders

重複の少ない大きなテーブルの場合、テーブル全体を別の場所にコピーしないようにすることができます。 1つの方法は、保持する行を保持する(重複するキーごとに)一時テーブルを作成し、元のテーブルから重複を削除することです。

例が与えられます here

1
user1931858

この単純なクエリを使用して、重複するエントリをすべて削除できます。重複するレコードがすべて選択されて削除されます。

 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
0
kamran Sheikh

私はこれが古いスレッドであることを知っていますが、私はかなり乱雑なメソッドを持っています100秒(10:1)。

私の方法は、あなたが避けようとしていたすべてのもの乱雑なものを必要としました:

  • グループ化(および所有)
  • oRDER BYを使用したグループ連結
  • 2つの一時テーブル
  • ディスク上のファイルを使用する!
  • どういうわけか(php?)後にファイルを削除

しかし、あなたが何百万(または私の場合は数千万)について話しているとき、それは価値があります。

とにかくコメントはポルトガル語で書かれているので大したことではありませんが、ここに私のサンプルがあります:

[〜#〜] edit [〜#〜]:コメントがあれば、その仕組みをさらに説明します:)

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
JDuarteDJ

MySQLの経験はあまりありません。分析機能がある場合:

 delete from my_tbl 
 where id in(
 select id 
 from(select id、row_number()
 over(partition by text1、text2 order text3 desc)as rn 
 from my_tbl 
/*オプション:text1 like 'a%' */
)as t2 
 where rn> 1 
)

オプションのwhere句は、文字ごとに1回など、複数回実行する必要があることを意味します。text1にインデックスを作成しますか?

これを実行する前に、「text desc」がNULLをMySQLで最後にソートすることを確認してください。

0
redcayuga