MySQL быстро удаляет дубликаты из большой базы данных

У меня большая (> миллионов строк) база данных MySQL, испорченная дубликатами. Я думаю, что ими может быть от 1/4 до 1/2 всего дБ. Мне нужно быстро избавиться от них (я имею в виду время выполнения запроса). Вот как это выглядит:
id (index) | 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)

Новые идентификаторы могут быть чем угодно, они не зависят от идентификаторов старых таблиц.
Я пробовал такие вещи, как:

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 минут)

Есть ли более быстрый способ сделать это? Пожалуйста, помогите мне решить эту проблему.


person bizzz    schedule 30.10.2009    source источник
comment
Уточните, пожалуйста: а) необходимо ли изменить нумерацию поля id? б) какое количество или соотношение дубликатов мы ожидаем? (полезно для принятия решения о работе на месте или создании новой таблицы) c) какие индексы существуют в текущей таблице.   -  person mjv    schedule 30.10.2009
comment
а) в перенумерации поля id нет необходимости б) моя оценка: от 1/4 до 1/2 db дублируются в) id - единственный индекс. Отредактирую вопрос соответственно.   -  person bizzz    schedule 31.10.2009


Ответы (9)


Я считаю, что это сделает это, используя дублирующий ключ + 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;

Должно быть намного быстрее, чем все, что требует группировки или отдельного, или подзапроса, или даже порядка. Для этого даже не требуется файловая сортировка, которая снижает производительность большой временной таблицы. По-прежнему потребуется полное сканирование исходной таблицы, но этого нельзя избежать.

person ʞɔıu    schedule 30.10.2009
comment
Спасибо, это работает! Строки размером 1,2 миллиона превратились в 0,6 миллиона за 60 минут, так что в минуту записывается около 10000 строк. Спасибо и за четкое объяснение! :) - person bizzz; 31.10.2009
comment
Это было большим подспорьем. Спасибо - person rpearce; 23.06.2012
comment
@ ʞɔıu (upsideDownNick) просто и эффективно. для тех, кому не важна часть text3 not null, вы можете использовать INSERT IGNORE (не учитывая часть ON DUPLICATE UPDATE), и mysql проигнорирует ошибки и вставит только первое отличное значение, которое он найдет (игнорируя последующие дубликаты). - person tony gil; 29.07.2012
comment
+1 Это умное решение. В моем случае клиент теряет соединение с сервером примерно через 10 минут (таблица содержит более 45 миллионов записей), в результате чего открываются беспорядочные блокировки и т. Д. - какие-либо рекомендации, как с этим справиться? - person Matt; 29.05.2013
comment
Если кому-то интересно, я расширил ответ @ ʞɔıu ДОПОЛНИТЕЛЬНЫМИ СЛУЧАЯМИ ИСПОЛЬЗОВАНИЯ на stackoverflow.com/questions/3311903/ - person César Revert-Gomar; 20.11.2017


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
)

Это запрашивает все записи, группы по полям различения и заказы по идентификатору (означает, что мы выбираем первую непустую запись text3). Затем мы выбираем идентификаторы из этого результата (это хорошие идентификаторы ... они не будут удалены) и удаляем все идентификаторы, которые их НЕ ИМЕЮТ.

Любой подобный запрос, затрагивающий всю таблицу, будет медленным. Вам просто нужно запустить его и дать развернуться, чтобы предотвратить его в будущем.

После того, как вы сделали это "исправление", я применил бы UNIQUE INDEX (text1, text2) к этой таблице. Чтобы предотвратить возможность дублирования в будущем.

Если вы хотите пойти по маршруту «создать новую таблицу и заменить старую». Вы можете использовать очень внутренний оператор выбора для создания своего оператора вставки.

Специфично для 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 для получения дополнительной информации.

person Kevin Peno    schedule 30.10.2009
comment
Извините, оба ваших предложения удаляют дубликаты, но не выбирают правильное поле text3 для сохранения (NULL остаются, пока есть NOT NULL альтернативы) - person bizzz; 31.10.2009

удалить дубликаты без удаления внешних ключей

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);
person Gadelkareem    schedule 10.06.2013
comment
Это должен быть правильный ответ. Просто и оперативно. - person PKHunter; 24.04.2014

Если вы можете создать новую таблицу, сделайте это с помощью уникального ключа в полях text1 + text2. Затем вставьте в таблицу, игнорируя ошибки (используя синтаксис INSERT IGNORE):

select * from my_tbl order by text3 desc
  • Я думаю, что порядок text3 desc поместит NULL последними, но дважды проверьте это.

Индексы для всех этих столбцов могут очень помочь, но их создание сейчас может быть довольно медленным.

person Scott Saunders    schedule 30.10.2009
comment
Он помещает нули последними, но не удовлетворяет запрос, который был первым, у которого нет нуля в text3. для этого вам нужно будет сделать заказ по идентификатору ASC и добавить в оператор WHERE text3 IS NOT NULL. - person Kevin Peno; 30.10.2009
comment
Неплохо подмечено. Однако это требование противоречит его образцу вывода: 2 | ааа | bbb | NULL Возможно, он расскажет нам, чего он действительно хочет. - person Scott Saunders; 30.10.2009
comment
Я перечитал его просьбу. Похоже, что ему все равно, если есть ненулевое значение, ненулевое значение сохраняется. Так что ваш пример подойдет. :) - person Kevin Peno; 31.10.2009
comment
Спасибо, работает. При длине рядов 1,2 мил на это ушло почти 3 часа; примерно 4000 строк в минуту записываются. Он оставляет дубликат с самым большим полем text3, что соответствует моей логике базы данных. - person bizzz; 31.10.2009

Для больших таблиц с несколькими дубликатами вы можете не копировать всю таблицу в другое место. Один из способов - создать временную таблицу, содержащую строки, которые вы хотите сохранить (для каждого ключа с дубликатами), а затем удалить дубликаты из исходной таблицы.

Пример приведен здесь.

person user1931858    schedule 14.08.2013

У меня нет большого опыта работы с MySQL. Если у него есть аналитические функции, попробуйте:

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
     )

необязательное предложение where означает, что вам придется запускать его несколько раз, по одному для каждой буквы и т. д. Создать индекс для text1?

Перед запуском убедитесь, что «text desc» будет отсортировать нули в MySQL последним.

person redcayuga    schedule 30.10.2009
comment
Извините, код ошибки: 1064 рядом с '(раздел по ...' - person bizzz; 31.10.2009
comment
Я думаю, MySql не имеет аналитических функций. Попробую позже. - person redcayuga; 03.11.2009
comment
Можете ли вы запустить: создать дубликаты таблиц как SELECT text1, text2, max (случай, когда text3 равен нулю, затем 1, иначе 0) как has_null3, max (случай, когда text3 не равен нулю, затем 1, иначе 0) как has_not_null3, min (случай, когда text3 равен not null then id else null) как pref_id FROM my_tbl GROUP BY text1, text2 с count (*) ›1 Это даст нам список дублированных text1 / 2 и некоторые из предпочтительных идентификаторов. Если это займет слишком много времени и, вероятно, так и будет, добавьте where text1, например 'a%' или что-то в этом роде. - person redcayuga; 03.11.2009

Я знаю, что это старый поток, но у меня есть несколько беспорядочный метод, который намного быстрее и настраивается, с точки зрения скорости я бы сказал 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;
person JDuarteDJ    schedule 16.07.2014

вы можете удалить все повторяющиеся записи с помощью этого простого запроса. который выберет все повторяющиеся записи и удалит их.

 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
person kamran Sheikh    schedule 02.04.2018