MySQL szybko usuń duplikaty z dużej bazy danych

Mam dużą (>milionową) bazę danych MySQL popsutą duplikatami. Myślę, że może to być od 1/4 do 1/2 całego db wypełnionego nimi. Muszę się ich szybko pozbyć(mam na myśli czas wykonania zapytania). Oto jak to wygląda:
id (index) / text1 | text2 / text3
kombinacja text1 i text2 powinna być unikalna, jeśli są jakieś duplikaty, powinna pozostać tylko jedna kombinacja z text3 nie NULL. Przykład:

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

...staje się:

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)

Nowe identyfikatory zimne być cokolwiek, robią nie zależy od starych ID tabeli.
Próbowałem takich rzeczy:

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;

Lub wybrać różne i inne odmiany.
Podczas gdy pracują na małych bazach danych, czas wykonywania zapytań na moim jest po prostu ogromny (właściwie nigdy nie dotarł do końca; > 20 min)

Czy jest na to jakiś szybszy sposób? Proszę, pomóż mi rozwiązać ten problem.
Author: bizzz, 2009-10-30

9 answers

Wierzę, że to da radę, używając duplicate key + 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;

Powinny być znacznie szybsze niż wszystko, co wymaga group by, distinctive lub subquery, a nawet order by. Nie wymaga to nawet sortowania plików, co spowoduje utratę wydajności na dużej tabeli tymczasowej. Nadal będzie wymagać pełnego skanowania oryginalnej tabeli, ale nie można tego uniknąć.

 146
Author: ʞɔıu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-30 21:34:19

Znalazłem ten prosty 1-liniowy kod, aby zrobić dokładnie to, czego potrzebowałem:

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

Wzięte z: http://mediakey.dk/~CC / mysql-remove-duplicate-entries/

 95
Author: liorq,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-18 09:39:11
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
)

To odpytuje wszystkie rekordy, grupuje po polach rozróżnienia i porządkuje po ID (czyli wybieramy pierwszy rekord nie null text3). Następnie wybieramy id z tego wyniku (są to dobre ID...nie zostaną usunięte) i usuń wszystkie identyfikatory, które nie są tymi.

Każde zapytanie takie jak to wpływające na całą tabelę będzie powolne. Wystarczy go uruchomić i pozwolić mu się rozwinąć, aby można było temu zapobiec w przyszłości.

Po wykonaniu tej "poprawki" zastosuję unikalny indeks (text1, text2) do tego stołu. Aby zapobiec możliwości duplikatów w przyszłości.

Jeśli chcesz przejść do trasy "Utwórz nową tabelę i zastąp starą". Możesz użyć instrukcji very inner select do utworzenia instrukcji insert.

Specyficzne dla MySQL (zakłada, że nowa tabela ma nazwę my_tbl2 i ma dokładnie taką samą strukturę):

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

Zobacz MySQL INSERT ... Wybierz , aby uzyskać więcej informacji.

 12
Author: Kevin Peno,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-30 21:20:41

Usuwanie duplikatów bez usuwania kluczy obcych

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
Author: gadelkareem,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-10 16:06:13

Jeśli możesz utworzyć nową tabelę, zrób to za pomocą unikalnego klucza na polach text1 + text2. Następnie wstaw do tabeli ignorowanie błędów (używając składni INSERT IGNORE):

select * from my_tbl order by text3 desc
  • myślę, że kolejność przez text3 desc spowoduje, że null będzie ostatni, ale sprawdź to jeszcze raz.

Indeksy na wszystkich tych kolumnach mogą bardzo pomóc, ale tworzenie ich teraz może być dość powolne.

 3
Author: Scott Saunders,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-30 20:08:26

W przypadku dużych tabel z kilkoma duplikatami należy unikać kopiowania całej tabeli w inne miejsce. Jednym ze sposobów jest utworzenie tymczasowej tabeli zawierającej wiersze, które chcesz zachować (dla każdego klucza z duplikatami), a następnie usunięcie duplikatów z oryginalnej tabeli.

Przykład jest podany TUTAJ .

 1
Author: user1931858,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-14 23:57:12

Nie mam dużego doświadczenia z MySQL. Jeśli ma funkcje analityczne spróbuj:

delete from my_tbl
 where id in (
     select id 
       from (select id, row_number()
                            over (partition by text1, text2 order by text3 desc) as rn
               from my_tbl
               /* optional: where text1 like 'a%'  */
             ) as t2
       where rn > 1
     )

Opcjonalna klauzula where sprawia, że będziesz musiał uruchomić ją kilka razy, po jednym dla każdej litery itp. Utworzyć indeks na text1?

Przed uruchomieniem, upewnij się, że "text desc" będzie sortować null jako ostatni w MySQL.

 0
Author: redcayuga,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-30 21:12:11

Wiem, że to stary wątek, ale mam nieco niechlujny sposób, który jest znacznie szybszy i konfigurowalny, pod względem szybkości powiedziałbym 10sec zamiast 100sec (10: 1).

Moja metoda wymaga tego wszystkiego brudnego rzeczy, których starałeś się uniknąć:

  • Group by (and Having)
  • Grupa Konkatedra z zamówieniem przez
  • 2 tabele tymczasowe
  • używanie plików na dysku!
  • jakoś (php?) usuwanie pliku po
Ale kiedy jesteś mówiąc o milionach (a w moim przypadku dziesiątkach milionów) warto.

Anyway its not much bo komentarz jest po portugalsku ale oto moja próbka:

EDIT : jak dostanę Komentarze to wyjaśnię dalej Jak to działa:)

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
Author: JDuarteDJ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-16 18:40:52

Możesz usunąć wszystkie zduplikowane wpisy za pomocą tego prostego zapytania. spowoduje to zaznaczenie wszystkich zduplikowanych rekordów i usunięcie ich.

 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
Author: kamran Sheikh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-02 12:29:25