Как безопасно удалить дубли в MySQL и не потерять связанные данные
Дубли в MySQL появляются после импортов, ручных правок, ошибок интеграций, отсутствия уникальных индексов или смены правил сопоставления. Удалять их опасно: у дублей могут быть связанные заказы, файлы, комментарии, остатки, цены и история.
Главная ошибка — найти дубли через GROUP BY и сразу удалить все лишние строки. Так легко потерять связанную информацию или оставить внешние ключи на удалённую запись.
Сначала найти дубли
Пример: есть товары с одинаковым артикулом у одного поставщика.
SELECT supplier_id, sku, COUNT(*) AS cnt
FROM supplier_products
WHERE sku IS NOT NULL AND sku != ''
GROUP BY supplier_id, sku
HAVING cnt > 1;
Такой запрос показывает группы дублей, но ещё не говорит, какую запись удалять. Нужно выбрать основную.
Посмотреть конкретную группу
Перед массовой чисткой нужно открыть несколько примеров руками.
SELECT *
FROM supplier_products
WHERE supplier_id = 10
AND sku = 'ABC-123'
ORDER BY id;
Важно понять, чем записи отличаются: датой создания, внешним ID, остатками, ценой, активностью, связями с системным товаром.
Выбрать основную запись
Правило выбора должно быть явным. Например:
- оставить запись с непустым external_id;
- оставить самую новую запись;
- оставить запись, связанную с заказами;
- оставить активную запись;
- оставить запись с максимальным id только если это действительно правильно.
Правило “оставить MIN(id)” удобно технически, но не всегда верно бизнес-логически.
Проверить связи
Перед удалением нужно найти таблицы, которые ссылаются на дубли.
SELECT COUNT(*)
FROM order_items
WHERE supplier_product_id IN (101, 102, 103);
Если связи есть, их нужно перенести на основную запись или отказаться от удаления.
UPDATE order_items
SET supplier_product_id = 101
WHERE supplier_product_id IN (102, 103);
Такое обновление нужно делать только после проверки, что 101 действительно основная запись.
Временная таблица для плана удаления
Хороший подход — сначала сформировать таблицу соответствий: какой дубль заменить на какую основную запись.
CREATE TEMPORARY TABLE duplicate_product_map (
duplicate_id INT NOT NULL,
main_id INT NOT NULL,
PRIMARY KEY (duplicate_id)
);
После заполнения такой таблицы можно проверить план до удаления.
SELECT *
FROM duplicate_product_map
LIMIT 50;
Транзакция
Если объём небольшой, перенос связей и удаление дублей лучше делать в транзакции.
START TRANSACTION;
UPDATE order_items oi
JOIN duplicate_product_map m ON m.duplicate_id = oi.supplier_product_id
SET oi.supplier_product_id = m.main_id;
DELETE sp
FROM supplier_products sp
JOIN duplicate_product_map m ON m.duplicate_id = sp.id;
COMMIT;
На больших таблицах транзакция может быть тяжёлой. Тогда лучше делать пакетами и иметь проверенный бэкап.
Добавить уникальный индекс после чистки
Если после удаления дублей не добавить ограничение, дубли появятся снова.
CREATE UNIQUE INDEX ux_supplier_products_supplier_sku
ON supplier_products (supplier_id, sku);
Перед добавлением индекса нужно убедиться, что пустые значения и NULL обрабатываются так, как нужно проекту.
Не забыть логи и бэкап
Перед чисткой нужен бэкап. После чистки — отчёт: сколько групп дублей найдено, сколько связей перенесено, сколько строк удалено, какие ошибки были.
Чек-лист
- Найти группы дублей через GROUP BY.
- Разобрать несколько групп вручную.
- Определить правило выбора основной записи.
- Проверить все связанные таблицы.
- Сформировать таблицу соответствий duplicate_id → main_id.
- Перенести связи на основную запись.
- Удалить только подтверждённые дубли.
- Добавить уникальный индекс или другое ограничение.
- Сохранить отчёт и проверить результат.
Удаление дублей — это не одна SQL-команда, а маленькая миграция данных. Чем важнее таблица, тем осторожнее нужен план: бэкап, связи, основная запись, транзакция и защита от повторного появления дублей.
Комментарии (0)
Пока нет комментариев. Будьте первым!