Это хорошее решение для удаления дубликатов строк MySQL? - PullRequest
4 голосов
/ 21 июля 2010

Я видел решение создать альтернативную временную таблицу MySQL с уникальными строками, но мне не понравилась эта идея, так как мои таблицы очень большие и их было бы сложно переместить (и это могло бы создать огромные проблемы, если быошибки во время переезда).

Однако я обнаружил следующее.Что вы думаете об этом (где дубликаты для проверки - «field_name»)?

DELETE FROM table1
USING table1, table1 as vtable
WHERE (NOT table1.ID=vtable.ID)
AND (table1.field_name=vtable.field_name)

Кто-то сказал, что это должно сработать, но я не совсем уверен.Как вы думаете?Кроме того, наличие индексов вообще изменит производительность этой команды, скажем, наличие индекса для «field_name»?

РЕДАКТИРОВАТЬ: Есть ли способ проверить запрос перед его выполнением?Насколько я знаю, MySQL не поддерживает "объяснение" в запросах DELETE.

Ответы [ 3 ]

4 голосов
/ 21 июля 2010

Обратите внимание, что показанный вами запрос удалит оба дубликата. Я предполагаю, что вы хотите оставить один или другой.

Вот как бы я написал этот запрос:

DELETE t1 FROM table1 AS t1 JOIN table1 AS t2 
  ON t1.id > t2.id AND t1.field_name = t2.field_name;

Используя вместо "not-equals-to" больше-чем, вы удаляете только одну строку (более позднюю) вместо обеих.

Может помочь составной индекс над (id, field_name). Вы должны подтвердить это с помощью MySQL EXPLAIN, чтобы получить отчет по оптимизации. Но EXPLAIN поддерживает только SELECT запросы, поэтому вы должны выполнить эквивалентный SELECT для подтверждения оптимизации:

EXPLAIN SELECT * FROM table1 AS t1 JOIN table1 AS t2 
  ON t1.id > t2.id AND t1.field_name = t2.field_name;

Вы также спрашивали о тестировании. Я бы рекомендовал скопировать образец строк, содержащих дубликаты, в таблицу в вашей базе данных test:

CREATE TABLE test.table1test SELECT * FROM realdb.table1 LIMIT 10000;

Теперь вы можете проводить эксперименты с данными вашего образца, пока не убедитесь, что решение DELETE является правильным.

USE test;
SET autocommit = 0;
DELETE ... 
ROLLBACK;

Я бы порекомендовал назвать вашу скретч-таблицу в базе данных test чем-то отличным от вашей реальной таблицы в вашей реальной базе данных. На всякий случай, если вы запускаете экспериментальный DELETE, в то время как вы все еще случайно используете вашу реальную базу данных в качестве базы данных по умолчанию!


Ваши комментарии:

USE test - встроенная команда клиента mysql. Он устанавливает базу данных test в качестве базы данных по умолчанию. Это будет база данных по умолчанию, когда вы называете таблицы в своих запросах, не квалифицируя их с именем базы данных. См http://dev.mysql.com/doc/refman/5.1/en/use.html

SET autocommit = 0 отключает поведение по умолчанию для фиксации транзакции для каждого запроса неявно. Таким образом, вы должны явно дать команду COMMIT или ROLLBACK, чтобы завершить транзакцию. См http://dev.mysql.com/doc/refman/5.1/en/commit.html

Стоит использовать ROLLBACK, когда вы экспериментируете, потому что он отбрасывает изменения, сделанные в этой транзакции. Это быстрый способ вернуться к исходному состоянию ваших тестовых данных, чтобы вы могли попробовать другой эксперимент.

DELETE t1 это не опечатка. DELETE удаляет строки, а не целые таблицы. t1 - это псевдоним каждой строки , которая удовлетворяет условиям оператора (хотя возможно, что условия включают каждую строку в таблице). См. Описание удаления нескольких таблиц на http://dev.mysql.com/doc/refman/5.1/en/delete.html

Вроде как, когда вы запускаете цикл в PHP и используете переменную для итерации по циклу: for ($i=0; $i<100; ++$i) ... Переменная $i принимает ряд значений, и каждый раз, когда в цикле другое значение.

Вот демонстрация, показывающая, как мое решение удаляет несколько дубликатов. Я запустил это в своей базе данных test и вставил результат прямо из моего командного окна:

mysql> create table table1 (id serial primary key, field_name varchar(10));
Query OK, 0 rows affected (0.45 sec)

mysql> insert into table1 (field_name) 
       values (42), (42), (42), (42), (42), (42);
Query OK, 6 rows affected (0.00 sec)
Records: 6  Duplicates: 0  Warnings: 0

mysql> select * from table1;
+----+------------+
| id | field_name |
+----+------------+
|  1 | 42         | 
|  2 | 42         | 
|  3 | 42         | 
|  4 | 42         | 
|  5 | 42         | 
|  6 | 42         | 
+----+------------+
6 rows in set (0.00 sec)

mysql> delete t1 from table1 t1 join table1 t2 
       on t1.id > t2.id and t1.field_name = t2.field_name;
Query OK, 5 rows affected (0.00 sec)

mysql> select * from table1;
+----+------------+
| id | field_name |
+----+------------+
|  1 | 42         | 
+----+------------+
1 row in set (0.00 sec)
0 голосов
/ 21 июля 2010

Используемый мной метод позволяет избежать условия JOIN и должен быть значительно быстрее:

DELETE FROM table1 WHERE id NOT IN (SELECT MIN(x.id) FROM table1 AS x GROUP BY x.field_name);

Подвыбор собирает список идентификаторов, которые вы хотите сохранить. Это позволит вам сохранить уникальный ряд для каждого field_name. Оператор DELETE удалит все лишние повторяющиеся строки.

Кроме того, да, индекс в поле field_name повысит производительность вашего запроса.

0 голосов
/ 21 июля 2010

Этот запрос должен работать. Наличие индексов изменит производительность, но на самом деле это зависит от размера таблицы.

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

Помните, что всегда выполняйте резервное копирование таблиц перед выполнением любых основных пакетных заданий, чтобы вы всегда могли выполнить откат.

...