MySQL быстро удаляет дубликаты из большой базы данных
-
22-07-2019 - |
Вопрос
У меня большая (> миллионов строк) база данных MySQL, испорченная дубликатами.Я думаю, что ими может быть заполнено от 1/4 до 1/2 всей БД.Мне нужно быстро от них избавиться (я имею в виду время выполнения запроса).Вот как это выглядит:
id (индекс) | Текст1 | Текст2 | Текст3
Комбинация Text1 и Text2 должна быть уникальной, если есть какие -либо дубликаты, только одна комбинация с текстом3 не нулевой должна оставаться.Пример:
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;
Или ВЫБЕРИТЕ ОТЛИЧНЫЕ и другие варианты.
Хотя они работают с небольшими базами данных, время выполнения запроса у меня просто огромно (на самом деле оно никогда не доводилось до конца;> 20 мин)
Есть ли более быстрый способ сделать это?Пожалуйста, помогите мне решить эту проблему.
Решение
Я верю, что это будет сделано, используя дубликат ключа + 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;
Должно быть намного быстрее, чем что-либо, что требует группирования по отдельности или подзапроса, или даже упорядочения по. Это даже не требует файловой сортировки, которая снижает производительность на большой временной таблице. Все равно потребуется полное сканирование исходной таблицы, но этого избежать нельзя.
Другие советы
Нашел этот простой 1-строчный код, чтобы сделать именно то, что мне нужно:
ALTER IGNORE TABLE dupTest ADD UNIQUE INDEX(a,b);
Взято из: http://mediakey.dk/~cc/mysql-remove-duplicate-entries/ р>
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).Затем мы выбираем идентификаторы из этого результата (это хорошие идентификаторы... они не будут удалены) и удаляем все идентификаторы, которые НЕ являются таковыми.
Любой подобный запрос, затрагивающий всю таблицу, будет медленным.Вам просто нужно запустить его и дать ему развернуться, чтобы предотвратить его в будущем.
После того, как вы сделали это «исправление», я бы применил УНИКАЛЬНЫЙ ИНДЕКС (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 ВСТАВКА...ВЫБИРАТЬ Чтобы получить больше информации.
удалить дубликаты без удаления внешних ключей
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);
Если вы можете создать новую таблицу, сделайте это с помощью уникального ключа в полях text1 + text2.Затем вставьте в таблицу, игнорируя ошибки (используя синтаксис INSERT IGNORE):
select * from my_tbl order by text3 desc
- Я думаю, что порядок по убыванию text3 будет помещать NULL в последнюю очередь, но проверьте это дважды.
Индексы по всем этим столбцам могли бы очень помочь, но их создание сейчас может оказаться довольно медленным.
Для больших таблиц с несколькими дубликатами вы можете избежать копирования всей таблицы в другое место. Один из способов - создать временную таблицу, содержащую строки, которые вы хотите сохранить (для каждого ключа с дубликатами), а затем удалить дубликаты из исходной таблицы. Р>
Пример приведен здесь .
У меня нет большого опыта работы с 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.
Я знаю, что это старая тема, но у меня есть несколько неряшливый метод, который намного быстрее и настраивается, с точки зрения скорости я бы сказал 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;
Вы можете удалить все повторяющиеся записи с помощью этого простого запроса. это выберет все дубликаты записей и удалит их.
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